Compare commits

..

60 Commits

Author SHA1 Message Date
刘祥超
6255573e2e 版本号修改为1.3.8 2024-04-30 19:07:25 +08:00
刘祥超
26bcb3b590 可以在集群设置中修改节点最大并发读/写数 2024-04-30 19:07:17 +08:00
刘祥超
fd2ae5d445 修改Docker文件版本号为1.3.7 2024-04-29 23:15:24 +08:00
刘祥超
d51370d443 版本修改为1.3.7 2024-04-29 23:12:21 +08:00
刘祥超
2d711408c6 管理平台界面设置中增加“使用的DNS解析库”选项 2024-04-29 15:07:09 +08:00
刘祥超
e911039e33 更新依赖库 2024-04-29 14:24:40 +08:00
刘祥超
e915deffb3 安装MySQL过程中测试MySQL是否能够连接后关闭连接 2024-04-28 11:41:07 +08:00
刘祥超
3b61871399 完善添加/修改源站时“回源主机名”的提示文字 2024-04-25 10:24:20 +08:00
刘祥超
cb33d58685 Docker版本号修改为1.3.6 2024-04-22 11:17:20 +08:00
刘祥超
1135bfafd5 版本号变更为1.3.6 2024-04-22 10:55:55 +08:00
刘祥超
ac9716c355 升级依赖库 2024-04-21 20:56:13 +08:00
刘祥超
f501907264 修改Dockerfile中的版本号为1.3.5 2024-04-21 19:58:05 +08:00
刘祥超
39774542a5 修复otp相关编译问题 2024-04-21 14:30:13 +08:00
刘祥超
783b81bc45 版本号修改为1.3.5 2024-04-20 22:46:48 +08:00
刘祥超
af57cb0fed 优化文字提示 2024-04-20 10:20:10 +08:00
刘祥超
6470fbe9f7 提交components.js 2024-04-20 10:15:39 +08:00
刘祥超
19f3be2895 OTP认证二维码增加下载链接 2024-04-20 10:15:31 +08:00
刘祥超
8404dde243 更新依赖库/修复用户OTP中的产品名称 2024-04-20 10:00:15 +08:00
刘祥超
aa52d695e6 集群设置--网站设置中增加“自动gzip回源”选项 2024-04-18 16:15:40 +08:00
刘祥超
4c83d2cbc3 优化源站列表显示 2024-04-18 15:27:08 +08:00
刘祥超
014973a575 优化源站列表 2024-04-18 15:17:31 +08:00
刘祥超
84c8cb3450 源站地址填写为HTTP时,自动检测HTTPS是否可用 2024-04-18 11:31:47 +08:00
刘祥超
9aba52c276 防盗链功能增加“例外URL“和“限制URL”设置 2024-04-17 15:45:06 +08:00
刘祥超
bbfeb5ec16 UA名单支持批量添加关键词 2024-04-17 15:30:24 +08:00
刘祥超
25f2c68738 UA名单功能增加“例外URL“和“限制URL”设置 2024-04-17 15:10:12 +08:00
刘祥超
cbd2419f63 例外和限制URL中增加常见图片、常见音频、常见视频等规则 2024-04-17 14:46:40 +08:00
刘祥超
b61ffef9e8 内容压缩功能增加“例外URL“和“限制URL”设置 2024-04-17 13:54:55 +08:00
刘祥超
18fcc43326 CC规则默认忽略常用文件 2024-04-16 14:14:31 +08:00
刘祥超
dd63154bf7 版本号修改为1.3.4.4 2024-04-16 13:52:58 +08:00
刘祥超
fd2fce8a8d 取消内容压缩中的级别设置 2024-04-16 11:19:01 +08:00
刘祥超
48c90786e9 优化“预热超时时间”输入框长度 2024-04-15 21:15:33 +08:00
刘祥超
152c679565 版本号修改为1.3.4.3 2024-04-15 09:26:25 +08:00
刘祥超
1e428c6a71 删除一直未实现的Unix协议相关内容 2024-04-14 17:12:42 +08:00
刘祥超
2b26064c7d 限制TCP和UDP网站操作HTTP网站的功能 2024-04-14 16:45:17 +08:00
刘祥超
c25c713ac1 源站增加快速停用/启用功能 2024-04-14 16:29:57 +08:00
刘祥超
141fb7bc61 优化WAF CC规则的文字提示 2024-04-13 19:22:45 +08:00
刘祥超
436ee75709 简化IP名单中创建IP操作/支持IP以CIDR方式显示 2024-04-13 16:45:32 +08:00
刘祥超
939b925dfa 优化修改IP名单中IP信息 2024-04-13 10:07:45 +08:00
刘祥超
9fff1f4d03 版本号修改为1.3.4.2 2024-04-09 10:05:28 +08:00
刘祥超
68d2586120 增加URL通配符示例 2024-04-09 09:46:02 +08:00
刘祥超
6050df45e8 提交components.js 2024-04-08 14:57:17 +08:00
刘祥超
ad664f6d60 将版本号修改为1.3.4.1 2024-04-08 14:56:43 +08:00
刘祥超
0bc2894700 增加"edge-admin security.reset"命令来使用命令行重置安全设置 2024-04-08 11:38:04 +08:00
刘祥超
b0d63c0d5b 系统安全设置中增加“仅从自定义报头中获取IP”选项 2024-04-08 11:21:56 +08:00
刘祥超
3fa1cf5a2c 用户使用反向代理访问系统时主动引导用户设置“自定义客户端IP报头“ 2024-04-08 11:07:51 +08:00
刘祥超
33a5c86beb 使用本地SID二次校验增强管理系统安全性 2024-04-08 10:24:10 +08:00
刘祥超
37441b26f1 优化WAF策略默认设置
* 增加JSCookie动作选项
* 拦截动作增加“失败全局封禁”选项
* 人机识别动作增加“失败全局封禁”选项
* IP名单中的“服务”文字改为“网站”
2024-04-07 14:20:17 +08:00
刘祥超
9f123b9a26 优化IP名单相关文字提示 2024-04-06 19:33:53 +08:00
刘祥超
8b6b6319b5 正在执行中的缓存任务状态从“执行中”改为“部分完成” 2024-04-06 15:27:39 +08:00
刘祥超
5bce566988 IP检查结果也可以显示IPv6范围 2024-04-06 15:14:51 +08:00
刘祥超
ca0c845759 IP名单支持IPv6范围 2024-04-06 14:55:29 +08:00
刘祥超
a60a7879a7 优化批量删除IP名单文字提示 2024-04-06 10:38:21 +08:00
刘祥超
1b131b26ee 更好地支持IPv6 2024-04-06 10:07:53 +08:00
刘祥超
16f4fa6c3f 缓存策略类型从“内存缓存”切换到“文件缓存“时增加默认设置 2024-04-05 11:24:05 +08:00
刘祥超
fa94426133 优化节点CPU线程数文字提示 2024-04-04 20:31:29 +08:00
刘祥超
90484aaa24 集群设置增加自动硬盘TRIM选项 2024-04-04 17:04:28 +08:00
刘祥超
20fae556d7 “磁盘”文字改为“硬盘” 2024-04-04 16:48:49 +08:00
刘祥超
14e0610af8 更新依赖库 2024-04-03 08:14:33 +08:00
刘祥超
8ae708a682 取消MMAP相关选项修改限制 2024-03-30 08:43:38 +08:00
刘祥超
fc27d210bb 使用MMAP提升缓存读取性能 2024-03-29 19:30:12 +08:00
163 changed files with 2286 additions and 1377 deletions

View File

@@ -41,7 +41,8 @@ func main() {
Option("dev", "switch to 'dev' mode").
Option("prod", "switch to 'prod' mode").
Option("upgrade [--url=URL]", "upgrade from official site or an url").
Option("install-local-node", "install a local node")
Option("install-local-node", "install a local node").
Option("security.reset", "reset security config")
app.On("daemon", func() {
nodes.NewAdminNode().Daemon()
@@ -179,6 +180,17 @@ func main() {
log.Println("restarting ...")
app.RunRestart()
})
app.On("security.reset", func() {
var sock = gosock.NewTmpSock(teaconst.ProcessName)
if !sock.IsListening() {
fmt.Println("[ERROR]the service not started yet, you should start the service first")
return
}
_, _ = sock.Send(&gosock.Command{
Code: "security.reset",
})
fmt.Println("ok")
})
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.3.4
ENV VERSION 1.3.7
ENV ROOT_DIR /usr/local/goedge
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip

38
go.mod
View File

@@ -1,24 +1,24 @@
module github.com/TeaOSLab/EdgeAdmin
go 1.18
go 1.21
replace github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
require (
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
github.com/cespare/xxhash v1.1.0
github.com/cespare/xxhash/v2 v2.2.0
github.com/go-sql-driver/mysql v1.5.0
github.com/iwind/TeaGo v0.0.0-20231214114820-1bfb0013bcf6
github.com/iwind/TeaGo v0.0.0-20240429060313-31a7bc8e9cc9
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
github.com/miekg/dns v1.1.43
github.com/quic-go/quic-go v0.40.0
github.com/quic-go/quic-go v0.42.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.16.0
golang.org/x/sys v0.15.0
google.golang.org/grpc v1.45.0
github.com/xlzd/gotp v0.1.0
golang.org/x/crypto v0.22.0
golang.org/x/sys v0.19.0
google.golang.org/grpc v1.63.2
gopkg.in/yaml.v3 v3.0.1
)
@@ -26,29 +26,25 @@ 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/protobuf v1.5.3 // indirect
github.com/google/btree v1.0.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20231203200248-ad67f76aa53d // indirect
github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/onsi/ginkgo/v2 v2.13.2 // indirect
github.com/onsi/ginkgo/v2 v2.17.1 // 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-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
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
go.uber.org/mock v0.4.0 // indirect
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.24.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
golang.org/x/tools v0.19.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)

271
go.sum
View File

@@ -1,98 +1,39 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
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.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
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=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
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/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=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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.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-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/google/pprof v0.0.0-20240402174815-29b9bb013b0f h1:f00RU+zOX+B3rLAmMMkzHUF2h1z4DeYR9tTCvEq2REY=
github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/iwind/TeaGo v0.0.0-20240312020455-6f20b5121caf h1:WA9qgiynESu/DDTnLH6npRI5AK6UL9qwJ2YZ5qhJX5E=
github.com/iwind/TeaGo v0.0.0-20240312020455-6f20b5121caf/go.mod h1:SfqVbWyIPdVflyA6lMgicZzsoGS8pyeLiTRe8/CIpGI=
github.com/iwind/TeaGo v0.0.0-20240429060313-31a7bc8e9cc9 h1:lqSd+OeAMzPlejoVtsmHJEeQ6/MWXCr+Ws2eX9/Rewg=
github.com/iwind/TeaGo v0.0.0-20240429060313-31a7bc8e9cc9/go.mod h1:SfqVbWyIPdVflyA6lMgicZzsoGS8pyeLiTRe8/CIpGI=
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=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -101,24 +42,12 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
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.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.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
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=
github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
@@ -127,13 +56,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
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-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/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
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=
@@ -142,167 +68,66 @@ github.com/shirou/gopsutil/v3 v3.22.5 h1:atX36I/IXgFiB81687vSiBI5zrMsxcIBkP9cQMJ
github.com/shirou/gopsutil/v3 v3.22.5/go.mod h1:so9G9VzeHt/hsd0YwqprnjHnfARAUktauykSbr+y2gA=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tdewolff/minify/v2 v2.12.7 h1:pBzz2tAfz5VghOXiQIsSta6srhmTeinQPjRDHWoumCA=
github.com/tdewolff/minify/v2 v2.12.7/go.mod h1:ZRKTheiOGyLSK8hOZWWv+YoJAECzDivNgAlVYDHp/Ws=
github.com/tdewolff/parse/v2 v2.6.6 h1:Yld+0CrKUJaCV78DL1G2nk3C9lKrxyRTux5aaK/AkDo=
github.com/tdewolff/parse/v2 v2.6.6/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tealeg/xlsx/v3 v3.2.3 h1:MXnVh+9Y8cUglowItTy2HL3Kv6z+q/0aNjeKuTsVqZQ=
github.com/tealeg/xlsx/v3 v3.2.3/go.mod h1:0hGmAEoZ48SS1ZAE6eqZJkJVXgOMY+8a33vjXa8S8HA=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
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/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
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.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-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=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
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.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=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
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.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=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.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.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-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-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
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/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
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=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e h1:fNKDNuUyC4WH+inqDMpfXDdfvwfYILbsX+oskGZ8hxg=
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -93,6 +93,7 @@ func loadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
}
var config = &systemconfigs.AdminUIConfig{}
config.DNSResolver.Type = nodeconfigs.DNSResolverTypeDefault // 默认值
err = json.Unmarshal(resp.ValueJSON, config)
if err != nil {
logs.Println("[UI_MANAGER]" + err.Error())
@@ -108,7 +109,7 @@ func loadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
}
func defaultAdminUIConfig() *systemconfigs.AdminUIConfig {
return &systemconfigs.AdminUIConfig{
var config = &systemconfigs.AdminUIConfig{
ProductName: langs.DefaultMessage(codes.AdminUI_DefaultProductName),
AdminSystemName: langs.DefaultMessage(codes.AdminUI_DefaultSystemName),
ShowOpenSourceInfo: true,
@@ -117,6 +118,8 @@ func defaultAdminUIConfig() *systemconfigs.AdminUIConfig {
DefaultPageSize: 10,
TimeZone: nodeconfigs.DefaultTimeZoneLocation,
}
config.DNSResolver.Type = nodeconfigs.DNSResolverTypeDefault
return config
}
// 修改时区

View File

@@ -79,7 +79,7 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
return nil, err
}
if len(resp.ValueJSON) == 0 {
sharedSecurityConfig = defaultSecurityConfig()
sharedSecurityConfig = NewSecurityConfig()
return sharedSecurityConfig, nil
}
@@ -94,7 +94,7 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
err = json.Unmarshal(resp.ValueJSON, config)
if err != nil {
logs.Println("[SECURITY_MANAGER]" + err.Error())
sharedSecurityConfig = defaultSecurityConfig()
sharedSecurityConfig = NewSecurityConfig()
return sharedSecurityConfig, nil
}
err = config.Init()
@@ -105,7 +105,8 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
return sharedSecurityConfig, nil
}
func defaultSecurityConfig() *systemconfigs.SecurityConfig {
// NewSecurityConfig create new security config
func NewSecurityConfig() *systemconfigs.SecurityConfig {
return &systemconfigs.SecurityConfig{
Frame: FrameSameOrigin,
AllowLocal: true,

View File

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

View File

@@ -3,10 +3,12 @@ package nodes
import (
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/events"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/iwind/TeaGo"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/lists"
@@ -86,6 +88,9 @@ func (this *AdminNode) Run() {
// 启动API节点
this.startAPINode()
// 设置DNS相关
this.setupDNS()
// 启动IP库
this.startIPLibrary()
@@ -253,6 +258,33 @@ func (this *AdminNode) addPortsToFirewall() {
utils.AddPortsToFirewall(ports)
}
// 设置DNS相关
func (this *AdminNode) setupDNS() {
config, loadErr := configloaders.LoadAdminUIConfig()
if loadErr != nil {
// 默认使用go原生
err := os.Setenv("GODEBUG", "netdns=go")
if err != nil {
logs.Println("[DNS_RESOLVER]set env failed: " + err.Error())
}
return
}
var err error
switch config.DNSResolver.Type {
case nodeconfigs.DNSResolverTypeGoNative:
err = os.Setenv("GODEBUG", "netdns=go")
case nodeconfigs.DNSResolverTypeCGO:
err = os.Setenv("GODEBUG", "netdns=cgo")
default:
// 默认使用go原生
err = os.Setenv("GODEBUG", "netdns=go")
}
if err != nil {
logs.Println("[DNS_RESOLVER]set env failed: " + err.Error())
}
}
// 启动API节点
func (this *AdminNode) startAPINode() {
var configPath = Tea.Root + "/edge-api/configs/api.yaml"
@@ -406,6 +438,10 @@ func (this *AdminNode) listenSock() error {
case "prod": // 切换到prod
Tea.Env = Tea.EnvProd
_ = cmd.ReplyOk()
case "security.reset":
var newConfig = configloaders.NewSecurityConfig()
_ = configloaders.UpdateSecurityConfig(newConfig)
_ = cmd.ReplyOk()
}
})

View File

@@ -72,6 +72,9 @@ func (this *SessionManager) Read(sid string) map[string]string {
}
func (this *SessionManager) WriteItem(sid string, key string, value string) bool {
// 删除缓存
defer ttlcache.DefaultCache.Delete( "SESSION@" + sid)
// 忽略OTP
if strings.HasSuffix(sid, "_otp") {
return false
@@ -95,6 +98,9 @@ func (this *SessionManager) WriteItem(sid string, key string, value string) bool
}
func (this *SessionManager) Delete(sid string) bool {
// 删除缓存
defer ttlcache.DefaultCache.Delete( "SESSION@" + sid)
// 忽略OTP
if strings.HasSuffix(sid, "_otp") {
return false

View File

@@ -2,47 +2,65 @@ package utils
import (
"bytes"
"encoding/binary"
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/iwind/TeaGo/types"
"math/big"
"net"
"regexp"
"strings"
)
// IP2Long 将IP转换为整型
func IP2Long(ip string) uint64 {
s := net.ParseIP(ip)
if len(s) != 16 {
return 0
// ParseIPValue 解析IP值
func ParseIPValue(value string) (newValue string, ipFrom string, ipTo string, ok bool) {
if len(value) == 0 {
return
}
if strings.Contains(ip, ":") { // IPv6
bigInt := big.NewInt(0)
bigInt.SetBytes(s.To16())
return bigInt.Uint64()
}
return uint64(binary.BigEndian.Uint32(s.To4()))
}
newValue = value
// IsIPv4 判断是否为IPv4
func IsIPv4(ip string) bool {
if !regexp.MustCompile(`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$`).MatchString(ip) {
return false
}
if IP2Long(ip) == 0 {
return false
}
return true
}
// ip1-ip2
if strings.Contains(value, "-") {
var pieces = strings.Split(value, "-")
if len(pieces) != 2 {
return
}
// IsIPv6 判断是否为IPv6
func IsIPv6(ip string) bool {
if !strings.Contains(ip, ":") {
return false
ipFrom = strings.TrimSpace(pieces[0])
ipTo = strings.TrimSpace(pieces[1])
if !iputils.IsValid(ipFrom) || !iputils.IsValid(ipTo) {
return
}
if !iputils.IsSameVersion(ipFrom, ipTo) {
return
}
if iputils.CompareIP(ipFrom, ipTo) > 0 {
ipFrom, ipTo = ipTo, ipFrom
newValue = ipFrom + "-" + ipTo
}
ok = true
return
}
return len(net.ParseIP(ip)) == net.IPv6len
// ip/mask
if strings.Contains(value, "/") {
cidr, err := iputils.ParseCIDR(value)
if err != nil {
return
}
return newValue, cidr.From().String(), cidr.To().String(), true
}
// single value
if iputils.IsValid(value) {
ipFrom = value
ok = true
return
}
return
}
// ExtractIP 分解IP

View File

@@ -5,86 +5,6 @@ import (
"testing"
)
func TestIP2Long(t *testing.T) {
for _, ip := range []string{
"0.0.0.1",
"0.0.0.2",
"127.0.0.1",
"192.0.0.2",
"255.255.255.255",
"2001:db8:0:1::101",
"2001:db8:0:1::102",
"2406:8c00:0:3409:133:18:203:0",
"2406:8c00:0:3409:133:18:203:158",
"2406:8c00:0:3409:133:18:203:159",
"2406:8c00:0:3409:133:18:203:160",
} {
t.Log(ip, " -> ", IP2Long(ip))
}
}
func TestIsIPv4(t *testing.T) {
type testIP struct {
ip string
ok bool
}
for _, item := range []testIP{
{
ip: "1",
ok: false,
},
{
ip: "192.168.0.1",
ok: true,
},
{
ip: "1.1.0.1",
ok: true,
},
{
ip: "255.255.255.255",
ok: true,
},
{
ip: "192.168.0.1233",
ok: false,
},
} {
if IsIPv4(item.ip) != item.ok {
t.Fatal(item.ip, "should be", item.ok)
}
}
}
func TestIsIPv6(t *testing.T) {
type testIP struct {
ip string
ok bool
}
for _, item := range []testIP{
{
ip: "1",
ok: false,
},
{
ip: "2406:8c00:0:3409:133:18:203:158",
ok: true,
},
{
ip: "2406::8c00:0:3409:133:18:203:158",
ok: false,
},
{
ip: "2001:db8:0:1::101",
ok: true,
},
} {
if IsIPv6(item.ip) != item.ok {
t.Fatal(item.ip, "should be", item.ok)
}
}
}
func TestExtractIP(t *testing.T) {
t.Log(ExtractIP("192.168.1.100"))
}
@@ -107,4 +27,4 @@ func TestNextIP_Copy(t *testing.T) {
var ip = net.ParseIP("192.168.1.100")
var nextIP = NextIP(ip)
t.Log(ip, nextIP)
}
}

View File

@@ -0,0 +1,31 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package otputils
import (
"net/url"
)
// FixIssuer fix issuer in otp provisioning url
func FixIssuer(urlString string) string {
u, err := url.Parse(urlString)
if err != nil {
return urlString
}
var query = u.Query()
if query != nil {
var issuerName = query.Get("issuer")
if len(issuerName) > 0 {
unescapedIssuerName, unescapeErr := url.QueryUnescape(issuerName)
if unescapeErr == nil {
query.Set("issuer", unescapedIssuerName)
u.RawQuery = query.Encode()
}
}
return u.String()
}
return urlString
}

View File

@@ -0,0 +1,18 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package otputils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/otputils"
"testing"
)
func TestFixIssuer(t *testing.T) {
var beforeURL = "otpauth://totp/GoEdge%25E7%25AE%25A1%25E7%2590%2586%25E5%2591%2598%25E7%25B3%25BB%25E7%25BB%259F:admin?issuer=GoEdge%25E7%25AE%25A1%25E7%2590%2586%25E5%2591%2598%25E7%25B3%25BB%25E7%25BB%259F&secret=Q3J4WNOWBRFLP3HI"
var afterURL = otputils.FixIssuer(beforeURL)
t.Log(afterURL)
if beforeURL == afterURL {
t.Fatal("'afterURL' should not be equal to 'beforeURL'")
}
}

View File

@@ -169,3 +169,14 @@ func (this *ParentAction) FailLang(messageCode langs.MessageCode, args ...any) {
func (this *ParentAction) FailFieldLang(field string, messageCode langs.MessageCode, args ...any) {
this.FailField(field, langs.Message(this.LangCode(), messageCode, args...))
}
func (this *ParentAction) FilterHTTPFamily() bool {
if this.Data.GetString("serverFamily") == "http" {
return false
}
this.ResponseWriter.WriteHeader(http.StatusNotFound)
_, _ = this.ResponseWriter.Write([]byte("page not found"))
return true
}

View File

@@ -3,9 +3,11 @@ package admins
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/otputils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/skip2/go-qrcode"
"github.com/xlzd/gotp"
)
@@ -19,7 +21,8 @@ func (this *OtpQrcodeAction) Init() {
}
func (this *OtpQrcodeAction) RunGet(params struct {
AdminId int64
AdminId int64
Download bool
}) {
loginResp, err := this.RPC().LoginRPC().FindEnabledLogin(this.AdminContext(), &pb.FindEnabledLoginRequest{
AdminId: params.AdminId,
@@ -29,19 +32,19 @@ func (this *OtpQrcodeAction) RunGet(params struct {
this.ErrorPage(err)
return
}
login := loginResp.Login
var login = loginResp.Login
if login == nil || !login.IsOn {
this.NotFound("adminLogin", params.AdminId)
return
}
loginParams := maps.Map{}
var loginParams = maps.Map{}
err = json.Unmarshal(login.ParamsJSON, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
secret := loginParams.GetString("secret")
var secret = loginParams.GetString("secret")
// 当前用户信息
adminResp, err := this.RPC().AdminRPC().FindEnabledAdmin(this.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: params.AdminId})
@@ -49,7 +52,7 @@ func (this *OtpQrcodeAction) RunGet(params struct {
this.ErrorPage(err)
return
}
admin := adminResp.Admin
var admin = adminResp.Admin
if admin == nil {
this.NotFound("admin", params.AdminId)
return
@@ -60,12 +63,19 @@ func (this *OtpQrcodeAction) RunGet(params struct {
this.ErrorPage(err)
return
}
url := gotp.NewDefaultTOTP(secret).ProvisioningUri(admin.Username, uiConfig.AdminSystemName)
data, err := qrcode.Encode(url, qrcode.Medium, 256)
var url = gotp.NewDefaultTOTP(secret).ProvisioningUri(admin.Username, uiConfig.AdminSystemName)
data, err := qrcode.Encode(otputils.FixIssuer(url), qrcode.Medium, 256)
if err != nil {
this.ErrorPage(err)
return
}
if params.Download {
var filename = "OTP-ADMIN-" + admin.Username + ".png"
this.AddHeader("Content-Disposition", "attachment; filename=\""+filename+"\";")
}
this.AddHeader("Content-Type", "image/png")
this.AddHeader("Content-Length", types.String(len(data)))
_, _ = this.Write(data)
}

View File

@@ -3,11 +3,11 @@ package node
import (
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/ipAddresses/ipaddressutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
@@ -178,7 +178,7 @@ func (this *DetailAction) RunGet(params struct {
for _, route := range dnsInfo.Routes {
var recordType = "A"
if utils.IsIPv6(addr.Ip) {
if iputils.IsIPv6(addr.Ip) {
recordType = "AAAA"
}
recordMaps = append(recordMaps, maps.Map{

View File

@@ -3,8 +3,8 @@
package dns
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
@@ -110,7 +110,7 @@ func (this *RecordsAction) RunGet(params struct {
var isResolved = false
if isInstalled && cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(node.IpAddr) > 0 {
var recordType = "A"
if utils.IsIPv6(node.IpAddr) {
if iputils.IsIPv6(node.IpAddr) {
recordType = "AAAA"
}
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
@@ -148,7 +148,7 @@ func (this *RecordsAction) RunGet(params struct {
var isResolved = false
if isInstalled && len(defaultRoute) > 0 {
var recordType = "A"
if utils.IsIPv6(node.IpAddr) {
if iputils.IsIPv6(node.IpAddr) {
recordType = "AAAA"
}
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{

View File

@@ -109,6 +109,7 @@ func (this *IndexAction) RunPost(params struct {
HttpAllNodeIPPageHTML string
HttpAllNodeIPShowPage bool
HttpAllEnableServerAddrVariable bool
HttpAllRequestOriginsWithEncodings bool
HttpAllDomainAuditingIsOn bool
HttpAllDomainAuditingPrompt string
@@ -217,6 +218,7 @@ func (this *IndexAction) RunPost(params struct {
config.HTTPAll.ForceLnRequest = params.HttpAllForceLnRequest
config.HTTPAll.LnRequestSchedulingMethod = params.HttpAllLnRequestSchedulingMethod
config.HTTPAll.EnableServerAddrVariable = params.HttpAllEnableServerAddrVariable
config.HTTPAll.RequestOriginsWithEncodings = params.HttpAllRequestOriginsWithEncodings
// 访问日志
config.HTTPAccessLog.IsOn = params.HttpAccessLogIsOn

View File

@@ -113,6 +113,9 @@ func (this *IndexAction) RunGet(params struct {
"autoRemoteStart": cluster.AutoRemoteStart,
"autoInstallNftables": cluster.AutoInstallNftables,
"autoSystemTuning": cluster.AutoSystemTuning,
"autoTrimDisks": cluster.AutoTrimDisks,
"maxConcurrentReads": cluster.MaxConcurrentReads,
"maxConcurrentWrites": cluster.MaxConcurrentWrites,
"sshParams": sshParams,
"domainName": fullDomainName,
}
@@ -141,6 +144,9 @@ func (this *IndexAction) RunPost(params struct {
AutoRemoteStart bool
AutoInstallNftables bool
AutoSystemTuning bool
AutoTrimDisks bool
MaxConcurrentReads int32
MaxConcurrentWrites int32
Must *actions.Must
}) {
@@ -196,7 +202,10 @@ func (this *IndexAction) RunPost(params struct {
AutoRemoteStart: params.AutoRemoteStart,
AutoInstallNftables: params.AutoInstallNftables,
AutoSystemTuning: params.AutoSystemTuning,
AutoTrimDisks: params.AutoTrimDisks,
SshParamsJSON: sshParamsJSON,
MaxConcurrentReads: params.MaxConcurrentReads,
MaxConcurrentWrites: params.MaxConcurrentWrites,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -80,6 +80,9 @@ func (this *CreateAction) RunPost(params struct {
SystemdServiceIsOn bool
AutoInstallNftables bool
AutoSystemTuning bool
AutoTrimDisks bool
MaxConcurrentReads int32
MaxConcurrentWrites int32
// DNS相关
DnsDomainId int64
@@ -149,6 +152,9 @@ func (this *CreateAction) RunPost(params struct {
GlobalServerConfigJSON: globalServerConfigJSON,
AutoInstallNftables: params.AutoInstallNftables,
AutoSystemTuning: params.AutoSystemTuning,
AutoTrimDisks: params.AutoTrimDisks,
MaxConcurrentReads: params.MaxConcurrentReads,
MaxConcurrentWrites: params.MaxConcurrentWrites,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -21,7 +21,7 @@ import (
"strings"
)
// CheckDiskPartitions 检查服务器盘空间
// CheckDiskPartitions 检查服务器盘空间
func CheckDiskPartitions(thresholdPercent float64) (path string, usage uint64, usagePercent float64, shouldWarning bool) {
partitions, err := disk.Partitions(false)
if err != nil {

View File

@@ -1,8 +1,8 @@
package clusters
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
@@ -107,7 +107,7 @@ func (this *ClusterAction) RunGet(params struct {
var isResolved = false
if isInstalled && cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(node.IpAddr) > 0 {
var recordType = "A"
if utils.IsIPv6(node.IpAddr) {
if iputils.IsIPv6(node.IpAddr) {
recordType = "AAAA"
}
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
@@ -145,7 +145,7 @@ func (this *ClusterAction) RunGet(params struct {
var isResolved = false
if isInstalled && len(defaultRoute) > 0 {
var recordType = "A"
if utils.IsIPv6(node.IpAddr) {
if iputils.IsIPv6(node.IpAddr) {
recordType = "AAAA"
}
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{

View File

@@ -1,8 +1,8 @@
package domains
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
@@ -65,7 +65,7 @@ func (this *NodesPopupAction) RunGet(params struct {
var isResolved = false
if len(route.Name) > 0 && len(node.IpAddr) > 0 && len(cluster.DnsName) > 0 {
var recordType = "A"
if utils.IsIPv6(node.IpAddr) {
if iputils.IsIPv6(node.IpAddr) {
recordType = "AAAA"
}
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
@@ -99,7 +99,7 @@ func (this *NodesPopupAction) RunGet(params struct {
var isResolved = false
if len(defaultRoute) > 0 {
var recordType = "A"
if utils.IsIPv6(node.IpAddr) {
if iputils.IsIPv6(node.IpAddr) {
recordType = "AAAA"
}
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{

View File

@@ -21,6 +21,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
stringutil "github.com/iwind/TeaGo/utils/string"
"net"
@@ -236,7 +237,10 @@ func (this *IndexAction) RunPost(params struct {
}
// 写入SESSION
params.Auth.StoreAdmin(adminId, params.Remember)
var localSid = rands.HexString(32)
this.Data["localSid"] = localSid
this.Data["ip"] = loginutils.RemoteIP(&this.ActionObject)
params.Auth.StoreAdmin(adminId, params.Remember, localSid)
// 记录日志
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})

View File

@@ -37,6 +37,10 @@ func RemoteIP(action *actions.ActionObject) string {
return ipValue
}
}
if securityConfig.ClientIPHeaderOnly {
return ""
}
}
}

View File

@@ -19,6 +19,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands"
stringutil "github.com/iwind/TeaGo/utils/string"
"github.com/xlzd/gotp"
"time"
@@ -132,7 +133,10 @@ func (this *OtpAction) RunPost(params struct {
}
// 写入SESSION
params.Auth.StoreAdmin(adminId, params.Remember)
var localSid = rands.HexString(32)
this.Data["localSid"] = localSid
this.Data["ip"] = loginutils.RemoteIP(&this.ActionObject)
params.Auth.StoreAdmin(adminId, params.Remember, localSid)
// 删除OTP SESSION
_, err = this.RPC().LoginSessionRPC().DeleteLoginSession(this.AdminContext(), &pb.DeleteLoginSessionRequest{Sid: sid})

View File

@@ -0,0 +1,14 @@
package login
import (
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Prefix("/login").
GetPost("/validate", new(ValidateAction)).
EndAll()
})
}

View File

@@ -0,0 +1,74 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package login
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index/loginutils"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/rands"
"net"
)
type ValidateAction struct {
actionutils.ParentAction
}
func (this *ValidateAction) Init() {
this.Nav("", "", "")
}
func (this *ValidateAction) RunGet(params struct {
From string
}) {
this.Data["from"] = params.From
this.Show()
}
func (this *ValidateAction) RunPost(params struct {
Must *actions.Must
LocalSid string
Ip string
}) {
var isOk bool
defer func() {
this.Data["isOk"] = isOk
if !isOk {
loginutils.UnsetCookie(&this.ActionObject)
this.Session().Delete()
}
this.Success()
}()
if len(params.LocalSid) == 0 || len(params.LocalSid) != 32 {
return
}
if len(params.Ip) == 0 {
return
}
if net.ParseIP(params.Ip) == nil {
return
}
if params.LocalSid == this.Session().GetString("@localSid") {
isOk = true
// renew ip and local sid
var newIP = loginutils.RemoteIP(&this.ActionObject)
var newLocalSid = rands.HexString(32)
this.Session().Write("@ip", newIP)
this.Session().Write("@localSid", newLocalSid)
this.Data["ip"] = newIP
this.Data["localSid"] = newLocalSid
return
}
}

View File

@@ -41,6 +41,7 @@ func (this *CreatePopupAction) RunPost(params struct {
MaxSizeJSON []byte
FetchTimeoutJSON []byte
SyncCompressionCache bool
EnableMMAP bool
Description string
IsOn bool
@@ -97,6 +98,7 @@ func (this *CreatePopupAction) RunPost(params struct {
OpenFileCache: openFileCacheConfig,
EnableSendfile: params.FileEnableSendfile,
MinFreeSize: minFreeSize,
EnableMMAP: params.EnableMMAP,
}
case serverconfigs.CachePolicyStorageMemory:
options = &serverconfigs.HTTPMemoryCacheStorage{}

View File

@@ -40,13 +40,23 @@ func (this *UpdateAction) RunGet(params struct {
return
}
// fix min free size
if cachePolicy.Type == serverconfigs.CachePolicyStorageFile && cachePolicy.Options != nil {
_, ok := cachePolicy.Options["minFreeSize"]
if !ok {
cachePolicy.Options["minFreeSize"] = &shared.SizeCapacity{
Count: 0,
Unit: shared.SizeCapacityUnitGB,
// fix min free size
{
_, ok := cachePolicy.Options["minFreeSize"]
if !ok {
cachePolicy.Options["minFreeSize"] = &shared.SizeCapacity{
Count: 0,
Unit: shared.SizeCapacityUnitGB,
}
}
}
// fix enableMMAP
{
_, ok := cachePolicy.Options["enableMMAP"]
if !ok {
cachePolicy.Options["enableMMAP"] = true
}
}
}
@@ -77,6 +87,8 @@ func (this *UpdateAction) RunPost(params struct {
SyncCompressionCache bool
FetchTimeoutJSON []byte
EnableMMAP bool
Description string
IsOn bool
@@ -137,6 +149,7 @@ func (this *UpdateAction) RunPost(params struct {
OpenFileCache: openFileCacheConfig,
EnableSendfile: params.FileEnableSendfile,
MinFreeSize: minFreeSize,
EnableMMAP: params.EnableMMAP,
}
case serverconfigs.CachePolicyStorageMemory:
options = &serverconfigs.HTTPMemoryCacheStorage{}

View File

@@ -99,6 +99,7 @@ func (this *ListsAction) RunGet(params struct {
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"value": item.Value,
"ipFrom": item.IpFrom,
"ipTo": item.IpTo,
"createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt),

View File

@@ -57,6 +57,7 @@ func (this *TestAction) RunPost(params struct {
if resp.IpItem != nil {
resultMap["item"] = maps.Map{
"id": resp.IpItem.Id,
"value": resp.IpItem.Value,
"ipFrom": resp.IpItem.IpFrom,
"ipTo": resp.IpItem.IpTo,
"reason": resp.IpItem.Reason,

View File

@@ -1,6 +1,7 @@
package ipadmin
import ( "github.com/TeaOSLab/EdgeAdmin/internal/utils"
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
@@ -32,6 +33,7 @@ func (this *UpdateIPPopupAction) RunGet(params struct {
this.Data["item"] = maps.Map{
"id": item.Id,
"value": item.Value,
"ipFrom": item.IpFrom,
"ipTo": item.IpTo,
"expiredAt": item.ExpiredAt,
@@ -49,8 +51,7 @@ func (this *UpdateIPPopupAction) RunPost(params struct {
FirewallPolicyId int64
ItemId int64
IpFrom string
IpTo string
Value string
ExpiredAt int64
Reason string
Type string
@@ -62,47 +63,25 @@ func (this *UpdateIPPopupAction) RunPost(params struct {
// 日志
defer this.CreateLogInfo(codes.WAF_LogUpdateIPFromWAFPolicy, params.FirewallPolicyId, params.ItemId)
// TODO 校验ItemId所属用户
switch params.Type {
case "ipv4":
case "ip":
// 校验IP格式
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入开始IP")
Field("value", params.Value).
Require("请输入IP或IP段")
// 校验IP格式ipFrom/ipTo
var ipFromLong uint64
if !utils.IsIPv4(params.IpFrom) {
this.Fail("请输入正确的开始IP")
}
ipFromLong = utils.IP2Long(params.IpFrom)
var ipToLong uint64
if len(params.IpTo) > 0 && !utils.IsIPv4(params.IpTo) {
this.Fail("请输入正确的结束IP")
}
ipToLong = utils.IP2Long(params.IpTo)
if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong {
params.IpTo, params.IpFrom = params.IpFrom, params.IpTo
}
case "ipv6":
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入IP")
// 校验IP格式ipFrom
if !utils.IsIPv6(params.IpFrom) {
this.Fail("请输入正确的IPv6地址")
_, _, _, ok := utils.ParseIPValue(params.Value)
if !ok {
this.FailField("value", "请输入正确的IP格式")
return
}
case "all":
params.IpFrom = "0.0.0.0"
params.Value = "0.0.0.0"
}
_, err := this.RPC().IPItemRPC().UpdateIPItem(this.AdminContext(), &pb.UpdateIPItemRequest{
IpItemId: params.ItemId,
IpFrom: params.IpFrom,
IpTo: params.IpTo,
Value: params.Value,
ExpiredAt: params.ExpiredAt,
Reason: params.Reason,
Type: params.Type,

View File

@@ -97,6 +97,7 @@ func (this *PolicyAction) RunGet(params struct {
"blockOptions": firewallPolicy.BlockOptions,
"pageOptions": firewallPolicy.PageOptions,
"captchaOptions": firewallPolicy.CaptchaOptions,
"jsCookieOptions": firewallPolicy.JSCookieOptions,
"useLocalFirewall": firewallPolicy.UseLocalFirewall,
"synFlood": firewallPolicy.SYNFlood,
"log": firewallPolicy.Log,

View File

@@ -10,7 +10,6 @@ import (
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"net/http"
)
type UpdateAction struct {
@@ -36,17 +35,17 @@ func (this *UpdateAction) RunGet(params struct {
// block options
if firewallPolicy.BlockOptions == nil {
firewallPolicy.BlockOptions = &firewallconfigs.HTTPFirewallBlockAction{
StatusCode: http.StatusForbidden,
Body: "Blocked By WAF",
URL: "",
Timeout: 60,
}
firewallPolicy.BlockOptions = firewallconfigs.NewHTTPFirewallBlockAction()
}
// page options
if firewallPolicy.PageOptions == nil {
firewallPolicy.PageOptions = firewallconfigs.DefaultHTTPFirewallPageAction()
firewallPolicy.PageOptions = firewallconfigs.NewHTTPFirewallPageAction()
}
// jscookie options
if firewallPolicy.JSCookieOptions == nil {
firewallPolicy.JSCookieOptions = firewallconfigs.NewHTTPFirewallJavascriptCookieAction()
}
// mode
@@ -79,6 +78,7 @@ func (this *UpdateAction) RunGet(params struct {
"blockOptions": firewallPolicy.BlockOptions,
"pageOptions": firewallPolicy.PageOptions,
"captchaOptions": firewallPolicy.CaptchaOptions,
"jsCookieOptions": firewallPolicy.JSCookieOptions,
"useLocalFirewall": firewallPolicy.UseLocalFirewall,
"synFloodConfig": firewallPolicy.SYNFlood,
"log": firewallPolicy.Log,
@@ -110,21 +110,22 @@ func (this *UpdateAction) RunGet(params struct {
}
func (this *UpdateAction) RunPost(params struct {
FirewallPolicyId int64
Name string
GroupCodes []string
BlockOptionsJSON []byte
PageOptionsJSON []byte
CaptchaOptionsJSON []byte
Description string
IsOn bool
Mode string
UseLocalFirewall bool
SynFloodJSON []byte
LogJSON []byte
MaxRequestBodySize int64
DenyCountryHTML string
DenyProvinceHTML string
FirewallPolicyId int64
Name string
GroupCodes []string
BlockOptionsJSON []byte
PageOptionsJSON []byte
CaptchaOptionsJSON []byte
JsCookieOptionsJSON []byte
Description string
IsOn bool
Mode string
UseLocalFirewall bool
SynFloodJSON []byte
LogJSON []byte
MaxRequestBodySize int64
DenyCountryHTML string
DenyProvinceHTML string
Must *actions.Must
}) {
@@ -136,7 +137,7 @@ func (this *UpdateAction) RunPost(params struct {
Require("请输入策略名称")
// 校验拦截选项JSON
var blockOptions = &firewallconfigs.HTTPFirewallBlockAction{}
var blockOptions = firewallconfigs.NewHTTPFirewallBlockAction()
err := json.Unmarshal(params.BlockOptionsJSON, blockOptions)
if err != nil {
this.Fail("拦截动作参数校验失败:" + err.Error())
@@ -144,7 +145,7 @@ func (this *UpdateAction) RunPost(params struct {
}
// 校验显示页面选项JSON
var pageOptions = &firewallconfigs.HTTPFirewallPageAction{}
var pageOptions = firewallconfigs.NewHTTPFirewallPageAction()
err = json.Unmarshal(params.PageOptionsJSON, pageOptions)
if err != nil {
this.Fail("校验显示页面动作配置失败:" + err.Error())
@@ -156,7 +157,7 @@ func (this *UpdateAction) RunPost(params struct {
}
// 校验验证码选项JSON
var captchaOptions = &firewallconfigs.HTTPFirewallCaptchaAction{}
var captchaOptions = firewallconfigs.NewHTTPFirewallCaptchaAction()
err = json.Unmarshal(params.CaptchaOptionsJSON, captchaOptions)
if err != nil {
this.Fail("验证码动作参数校验失败:" + err.Error())
@@ -180,6 +181,16 @@ func (this *UpdateAction) RunPost(params struct {
}
}
// 校验JSCookie选项JSON
var jsCookieOptions = firewallconfigs.NewHTTPFirewallJavascriptCookieAction()
if len(params.JsCookieOptionsJSON) > 0 {
err = json.Unmarshal(params.JsCookieOptionsJSON, jsCookieOptions)
if err != nil {
this.Fail("JSCookie动作参数校验失败" + err.Error())
return
}
}
// 最大内容尺寸
if params.MaxRequestBodySize < 0 {
params.MaxRequestBodySize = 0
@@ -194,6 +205,7 @@ func (this *UpdateAction) RunPost(params struct {
BlockOptionsJSON: params.BlockOptionsJSON,
PageOptionsJSON: params.PageOptionsJSON,
CaptchaOptionsJSON: params.CaptchaOptionsJSON,
JsCookieOptionsJSON: params.JsCookieOptionsJSON,
Mode: params.Mode,
UseLocalFirewall: params.UseLocalFirewall,
SynFloodJSON: params.SynFloodJSON,

View File

@@ -100,7 +100,6 @@ func (this *CreateAction) RunPost(params struct {
var httpsConfig *serverconfigs.HTTPSProtocolConfig = nil
var tcpConfig *serverconfigs.TCPProtocolConfig = nil
var tlsConfig *serverconfigs.TLSProtocolConfig = nil
var unixConfig *serverconfigs.UnixProtocolConfig = nil
var udpConfig *serverconfigs.UDPProtocolConfig = nil
var webId int64 = 0
@@ -428,14 +427,6 @@ func (this *CreateAction) RunPost(params struct {
}
req.TlsJSON = data
}
if unixConfig != nil {
data, err := json.Marshal(unixConfig)
if err != nil {
this.ErrorPage(err)
return
}
req.UnixJSON = data
}
if udpConfig != nil {
data, err := json.Marshal(udpConfig)
if err != nil {

View File

@@ -108,14 +108,6 @@ func (this *IndexAction) RunGet(params struct {
})
}
}
if config.Unix != nil && config.Unix.IsOn {
for _, listen := range config.Unix.Listen {
portMaps = append(portMaps, maps.Map{
"protocol": listen.Protocol,
"portRange": listen.Host,
})
}
}
if config.UDP != nil && config.UDP.IsOn {
for _, listen := range config.UDP.Listen {
portMaps = append(portMaps, maps.Map{

View File

@@ -109,6 +109,7 @@ func (this *AllowListAction) RunGet(params struct {
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"value": item.Value,
"ipFrom": item.IpFrom,
"ipTo": item.IpTo,
"createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt),

View File

@@ -109,6 +109,7 @@ func (this *DenyListAction) RunGet(params struct {
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"value": item.Value,
"ipFrom": item.IpFrom,
"ipTo": item.IpTo,
"createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt),

View File

@@ -70,6 +70,7 @@ func (this *TestAction) RunPost(params struct {
if resp.IpItem != nil {
resultMap["item"] = maps.Map{
"id": resp.IpItem.Id,
"value": resp.IpItem.Value,
"ipFrom": resp.IpItem.IpFrom,
"ipTo": resp.IpItem.IpTo,
"reason": resp.IpItem.Reason,

View File

@@ -33,6 +33,7 @@ func (this *UpdateIPPopupAction) RunGet(params struct {
this.Data["item"] = maps.Map{
"id": item.Id,
"value": item.Value,
"ipFrom": item.IpFrom,
"ipTo": item.IpTo,
"expiredAt": item.ExpiredAt,
@@ -49,8 +50,7 @@ func (this *UpdateIPPopupAction) RunGet(params struct {
func (this *UpdateIPPopupAction) RunPost(params struct {
ItemId int64
IpFrom string
IpTo string
Value string
ExpiredAt int64
Reason string
Type string
@@ -62,47 +62,25 @@ func (this *UpdateIPPopupAction) RunPost(params struct {
// 日志
defer this.CreateLogInfo(codes.IPItem_LogUpdateIPItem, params.ItemId)
// TODO 校验ItemId所属用户
switch params.Type {
case "ipv4":
case "ip":
// 校验IP格式
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入开始IP")
Field("value", params.Value).
Require("请输入IP或IP段")
// 校验IP格式ipFrom/ipTo
var ipFromLong uint64
if !utils.IsIPv4(params.IpFrom) {
this.Fail("请输入正确的开始IP")
}
ipFromLong = utils.IP2Long(params.IpFrom)
var ipToLong uint64
if len(params.IpTo) > 0 && !utils.IsIPv4(params.IpTo) {
this.Fail("请输入正确的结束IP")
}
ipToLong = utils.IP2Long(params.IpTo)
if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong {
params.IpTo, params.IpFrom = params.IpFrom, params.IpTo
}
case "ipv6":
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入IP")
// 校验IP格式ipFrom
if !utils.IsIPv6(params.IpFrom) {
this.Fail("请输入正确的IPv6地址")
_, _, _, ok := utils.ParseIPValue(params.Value)
if !ok {
this.FailField("value", "请输入正确的IP格式")
return
}
case "all":
params.IpFrom = "0.0.0.0"
params.Value = "0.0.0.0"
}
_, err := this.RPC().IPItemRPC().UpdateIPItem(this.AdminContext(), &pb.UpdateIPItemRequest{
IpItemId: params.ItemId,
IpFrom: params.IpFrom,
IpTo: params.IpTo,
Value: params.Value,
ExpiredAt: params.ExpiredAt,
Reason: params.Reason,
Type: params.Type,

View File

@@ -155,14 +155,6 @@ func (this *IndexAction) RunGet(params struct {
})
}
}
if config.Unix != nil && config.Unix.IsOn {
for _, listen := range config.Unix.Listen {
portMaps = append(portMaps, maps.Map{
"protocol": listen.Protocol,
"portRange": listen.Host,
})
}
}
if config.UDP != nil && config.UDP.IsOn {
for _, listen := range config.UDP.Listen {
portMaps = append(portMaps, maps.Map{

View File

@@ -1,15 +1,14 @@
package iplists
import ( "github.com/TeaOSLab/EdgeAdmin/internal/utils"
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"net"
"strings"
)
@@ -49,9 +48,7 @@ func (this *CreateIPPopupAction) RunPost(params struct {
ListId int64
Method string
IpFrom string
IpTo string
Value string
IpData string
ExpiredAt int64
@@ -75,33 +72,24 @@ func (this *CreateIPPopupAction) RunPost(params struct {
}
type ipData struct {
value string
ipFrom string
ipTo string
}
var batchIPs = []*ipData{}
switch params.Type {
case "ipv4":
case "ip":
if params.Method == "single" {
// 校验IP格式ipFrom/ipTo
// 校验IP格式
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入开始IP")
Field("value", params.Value).
Require("请输入IP或IP段")
var ipFromLong uint64
if !utils.IsIPv4(params.IpFrom) {
this.Fail("请输入正确的开始IP")
}
ipFromLong = utils.IP2Long(params.IpFrom)
var ipToLong uint64
if len(params.IpTo) > 0 && !utils.IsIPv4(params.IpTo) {
this.Fail("请输入正确的结束IP")
}
ipToLong = utils.IP2Long(params.IpTo)
if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong {
params.IpTo, params.IpFrom = params.IpFrom, params.IpTo
_, _, _, ok := utils.ParseIPValue(params.Value)
if !ok {
this.FailField("value", "请输入正确的IP格式")
return
}
} else if params.Method == "batch" {
if len(params.IpData) == 0 {
@@ -110,137 +98,30 @@ func (this *CreateIPPopupAction) RunPost(params struct {
var lines = strings.Split(params.IpData, "\n")
for index, line := range lines {
line = strings.TrimSpace(line)
if strings.Contains(line, "/") { // CIDR
if strings.Contains(line, ":") {
this.FailField("ipData", "第"+types.String(index+1)+"行IP格式错误"+line)
}
ipFrom, ipTo, err := configutils.ParseCIDR(line)
if err != nil {
this.FailField("ipData", "第"+types.String(index+1)+"行IP格式错误"+line)
}
batchIPs = append(batchIPs, &ipData{
ipFrom: ipFrom,
ipTo: ipTo,
})
} else if strings.Contains(line, "-") { // IP Range
var pieces = strings.Split(line, "-")
var ipFrom = strings.TrimSpace(pieces[0])
var ipTo = strings.TrimSpace(pieces[1])
if net.ParseIP(ipFrom) == nil || net.ParseIP(ipTo) == nil || strings.Contains(ipFrom, ":") || strings.Contains(ipTo, ":") {
this.FailField("ipData", "第"+types.String(index+1)+"行IP格式错误"+line)
}
if utils.IP2Long(ipFrom) > utils.IP2Long(ipTo) {
ipFrom, ipTo = ipTo, ipFrom
}
batchIPs = append(batchIPs, &ipData{
ipFrom: ipFrom,
ipTo: ipTo,
})
} else if strings.Contains(line, ",") { // IP Range
var pieces = strings.Split(line, ",")
var ipFrom = strings.TrimSpace(pieces[0])
var ipTo = strings.TrimSpace(pieces[1])
if net.ParseIP(ipFrom) == nil || net.ParseIP(ipTo) == nil || strings.Contains(ipFrom, ":") || strings.Contains(ipTo, ":") {
this.FailField("ipData", "第"+types.String(index+1)+"行IP格式错误"+line)
}
if utils.IP2Long(ipFrom) > utils.IP2Long(ipTo) {
ipFrom, ipTo = ipTo, ipFrom
}
batchIPs = append(batchIPs, &ipData{
ipFrom: ipFrom,
ipTo: ipTo,
})
} else if len(line) > 0 {
var ipFrom = line
if net.ParseIP(ipFrom) == nil || strings.Contains(ipFrom, ":") {
this.FailField("ipData", "第"+types.String(index+1)+"行IP格式错误"+line)
}
batchIPs = append(batchIPs, &ipData{
ipFrom: ipFrom,
})
if len(line) == 0 {
continue
}
}
}
case "ipv6":
if params.Method == "single" {
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入IP")
// 校验IP格式ipFrom
if !utils.IsIPv6(params.IpFrom) {
this.Fail("请输入正确的IPv6地址")
}
} else if params.Method == "batch" {
if len(params.IpData) == 0 {
this.FailField("ipData", "请输入IP")
}
var lines = strings.Split(params.IpData, "\n")
for index, line := range lines {
line = strings.TrimSpace(line)
if strings.Contains(line, "/") { // CIDR
if !strings.Contains(line, ":") {
this.FailField("ipData", "第"+types.String(index+1)+"行IP格式错误"+line)
}
ipFrom, ipTo, err := configutils.ParseCIDR(line)
if err != nil {
this.FailField("ipData", "第"+types.String(index+1)+"行IP格式错误"+line)
}
batchIPs = append(batchIPs, &ipData{
ipFrom: ipFrom,
ipTo: ipTo,
})
} else if strings.Contains(line, "-") { // IP Range
var pieces = strings.Split(line, "-")
var ipFrom = strings.TrimSpace(pieces[0])
var ipTo = strings.TrimSpace(pieces[1])
if net.ParseIP(ipFrom) == nil || net.ParseIP(ipTo) == nil || !strings.Contains(ipFrom, ":") || !strings.Contains(ipTo, ":") {
this.FailField("ipData", "第"+types.String(index+1)+"行IP格式错误"+line)
}
if utils.IP2Long(ipFrom) > utils.IP2Long(ipTo) {
ipFrom, ipTo = ipTo, ipFrom
}
batchIPs = append(batchIPs, &ipData{
ipFrom: ipFrom,
ipTo: ipTo,
})
} else if strings.Contains(line, ",") { // IP Range
var pieces = strings.Split(line, ",")
var ipFrom = strings.TrimSpace(pieces[0])
var ipTo = strings.TrimSpace(pieces[1])
if net.ParseIP(ipFrom) == nil || net.ParseIP(ipTo) == nil || !strings.Contains(ipFrom, ":") || !strings.Contains(ipTo, ":") {
this.FailField("ipData", "第"+types.String(index+1)+"行IP格式错误"+line)
}
if utils.IP2Long(ipFrom) > utils.IP2Long(ipTo) {
ipFrom, ipTo = ipTo, ipFrom
}
batchIPs = append(batchIPs, &ipData{
ipFrom: ipFrom,
ipTo: ipTo,
})
} else if len(line) > 0 {
var ipFrom = line
if net.ParseIP(ipFrom) == nil || !strings.Contains(ipFrom, ":") {
this.FailField("ipData", "第"+types.String(index+1)+"行IP格式错误"+line)
}
batchIPs = append(batchIPs, &ipData{
ipFrom: ipFrom,
})
_, ipFrom, ipTo, ok := utils.ParseIPValue(line)
if !ok {
this.FailField("ipData", "第"+types.String(index+1)+"行IP格式错误"+line)
return
}
batchIPs = append(batchIPs, &ipData{
value: line,
ipFrom: ipFrom,
ipTo: ipTo,
})
}
}
case "all":
params.IpFrom = "0.0.0.0"
params.Value = "0.0.0.0"
}
if len(batchIPs) > 0 {
for _, ip := range batchIPs {
_, err := this.RPC().IPItemRPC().CreateIPItem(this.AdminContext(), &pb.CreateIPItemRequest{
IpListId: params.ListId,
Value: ip.value,
IpFrom: ip.ipFrom,
IpTo: ip.ipTo,
ExpiredAt: params.ExpiredAt,
@@ -259,8 +140,7 @@ func (this *CreateIPPopupAction) RunPost(params struct {
} else {
createResp, err := this.RPC().IPItemRPC().CreateIPItem(this.AdminContext(), &pb.CreateIPItemRequest{
IpListId: params.ListId,
IpFrom: params.IpFrom,
IpTo: params.IpTo,
Value: params.Value,
ExpiredAt: params.ExpiredAt,
Reason: params.Reason,
Type: params.Type,

View File

@@ -30,7 +30,6 @@ func (this *ExportDataAction) RunGet(params struct {
}) {
defer this.CreateLogInfo(codes.IPList_LogExportIPList, params.ListId)
var err error
var ext string
var jsonMaps = []maps.Map{}
var xlsxFile *xlsx.File
@@ -44,20 +43,16 @@ func (this *ExportDataAction) RunGet(params struct {
case "xlsx":
ext = ".xlsx"
xlsxFile = xlsx.NewFile()
if err != nil {
this.ErrorPage(err)
return
}
var err error
xlsxSheet, err = xlsxFile.AddSheet("IP名单")
if err != nil {
this.ErrorPage(err)
return
}
row := xlsxSheet.AddRow()
var row = xlsxSheet.AddRow()
row.SetHeight(26)
row.AddCell().SetValue("开始IP")
row.AddCell().SetValue("结束IP")
row.AddCell().SetValue("IP/IP段")
row.AddCell().SetValue("过期时间戳")
row.AddCell().SetValue("类型")
row.AddCell().SetValue("级别")
@@ -93,27 +88,25 @@ func (this *ExportDataAction) RunGet(params struct {
for _, item := range itemsResp.IpItems {
switch params.Format {
case "xlsx":
row := xlsxSheet.AddRow()
var row = xlsxSheet.AddRow()
row.SetHeight(26)
row.AddCell().SetValue(item.IpFrom)
row.AddCell().SetValue(item.IpTo)
row.AddCell().SetValue(item.Value)
row.AddCell().SetValue(types.String(item.ExpiredAt))
row.AddCell().SetValue(item.Type)
row.AddCell().SetValue(item.EventLevel)
row.AddCell().SetValue(item.Reason)
case "csv":
err = csvWriter.Write([]string{item.IpFrom, item.IpTo, types.String(item.ExpiredAt), item.Type, item.EventLevel, item.Reason})
err = csvWriter.Write([]string{item.Value, types.String(item.ExpiredAt), item.Type, item.EventLevel, item.Reason})
if err != nil {
this.ErrorPage(err)
return
}
case "txt":
data = append(data, item.IpFrom+","+item.IpTo+","+types.String(item.ExpiredAt)+","+item.Type+","+item.EventLevel+","+item.Reason...)
data = append(data, item.Value+","+types.String(item.ExpiredAt)+","+item.Type+","+item.EventLevel+","+item.Reason...)
data = append(data, '\n')
case "json":
jsonMaps = append(jsonMaps, maps.Map{
"ipFrom": item.IpFrom,
"ipTo": item.IpTo,
"value": item.Value,
"expiredAt": item.ExpiredAt,
"type": item.Type,
"eventLevel": item.EventLevel,
@@ -127,7 +120,7 @@ func (this *ExportDataAction) RunGet(params struct {
switch params.Format {
case "xlsx":
var buf = &bytes.Buffer{}
err = xlsxFile.Write(buf)
err := xlsxFile.Write(buf)
if err != nil {
this.ErrorPage(err)
return
@@ -137,6 +130,7 @@ func (this *ExportDataAction) RunGet(params struct {
csvWriter.Flush()
data = csvBuffer.Bytes()
case "json":
var err error
data, err = json.Marshal(jsonMaps)
if err != nil {
this.ErrorPage(err)

View File

@@ -6,8 +6,8 @@ import (
"bytes"
"encoding/csv"
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
@@ -15,7 +15,6 @@ import (
"github.com/iwind/TeaGo/types"
"github.com/tealeg/xlsx/v3"
"io"
"net"
"regexp"
"strings"
)
@@ -79,9 +78,10 @@ func (this *ImportAction) RunPost(params struct {
var items = []*pb.IPItem{}
switch ext {
case ".xlsx":
file, err := xlsx.OpenBinary(data)
if err != nil {
this.Fail("Excel读取错误" + err.Error())
file, openErr := xlsx.OpenBinary(data)
if openErr != nil {
this.Fail("Excel读取错误" + openErr.Error())
return
}
if len(file.Sheets) > 0 {
var sheet = file.Sheets[0]
@@ -136,7 +136,7 @@ func (this *ImportAction) RunPost(params struct {
if len(line) == 0 {
continue
}
item := this.createItemFromValues(strings.SplitN(string(line), ",", 6), &countIgnore)
item := this.createItemFromValues(strings.SplitN(string(line), ",", 5), &countIgnore)
if item != nil {
items = append(items, item)
}
@@ -150,6 +150,7 @@ func (this *ImportAction) RunPost(params struct {
for _, item := range items {
_, err = this.RPC().IPItemRPC().CreateIPItem(this.AdminContext(), &pb.CreateIPItemRequest{
IpListId: params.ListId,
Value: item.Value,
IpFrom: item.IpFrom,
IpTo: item.IpTo,
ExpiredAt: item.ExpiredAt,
@@ -170,60 +171,45 @@ func (this *ImportAction) RunPost(params struct {
}
func (this *ImportAction) createItemFromValues(values []string, countIgnore *int) *pb.IPItem {
// ipFrom, ipTo, expiredAt, type, eventType, reason
// value, expiredAt, type, eventType, reason
var item = &pb.IPItem{}
switch len(values) {
case 1:
item.IpFrom = values[0]
item.Value = values[0]
case 2:
item.IpFrom = values[0]
item.IpTo = values[1]
item.Value = values[0]
item.ExpiredAt = types.Int64(values[1])
case 3:
item.IpFrom = values[0]
item.IpTo = values[1]
item.ExpiredAt = types.Int64(values[2])
item.Value = values[0]
item.ExpiredAt = types.Int64(values[1])
item.Type = values[2]
case 4:
item.IpFrom = values[0]
item.IpTo = values[1]
item.ExpiredAt = types.Int64(values[2])
item.Type = values[3]
item.Value = values[0]
item.ExpiredAt = types.Int64(values[1])
item.Type = values[2]
item.EventLevel = values[3]
case 5:
item.IpFrom = values[0]
item.IpTo = values[1]
item.ExpiredAt = types.Int64(values[2])
item.Type = values[3]
item.EventLevel = values[4]
case 6:
item.IpFrom = values[0]
item.IpTo = values[1]
item.ExpiredAt = types.Int64(values[2])
item.Type = values[3]
item.EventLevel = values[4]
item.Reason = values[5]
}
// CIDR
if strings.Contains(item.IpFrom, "/") {
ipFrom, ipTo, err := configutils.ParseCIDR(item.IpFrom)
if err == nil {
item.IpFrom = ipFrom
item.IpTo = ipTo
}
item.Value = values[0]
item.ExpiredAt = types.Int64(values[1])
item.Type = values[2]
item.EventLevel = values[3]
item.Reason = values[4]
}
if len(item.EventLevel) == 0 {
item.EventLevel = "critical"
}
if net.ParseIP(item.IpFrom) == nil {
*countIgnore++
return nil
}
if len(item.IpTo) > 0 && net.ParseIP(item.IpTo) == nil {
newValue, ipFrom, ipTo, ok := utils.ParseIPValue(item.Value)
if !ok {
*countIgnore++
return nil
}
item.Value = newValue
item.IpFrom = ipFrom
item.IpTo = ipTo
return item
}

View File

@@ -172,6 +172,7 @@ func (this *IndexAction) RunGet(params struct {
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"value": item.Value,
"ipFrom": item.IpFrom,
"ipTo": item.IpTo,
"createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt),

View File

@@ -118,6 +118,7 @@ func (this *ItemsAction) RunGet(params struct {
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"value": item.Value,
"ipFrom": item.IpFrom,
"ipTo": item.IpTo,
"createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt),

View File

@@ -58,6 +58,7 @@ func (this *TestAction) RunPost(params struct {
if resp.IpItem != nil {
resultMap["item"] = maps.Map{
"id": resp.IpItem.Id,
"value": resp.IpItem.Value,
"ipFrom": resp.IpItem.IpFrom,
"ipTo": resp.IpItem.IpTo,
"reason": resp.IpItem.Reason,

View File

@@ -1,6 +1,7 @@
package iplists
import ( "github.com/TeaOSLab/EdgeAdmin/internal/utils"
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
@@ -32,6 +33,7 @@ func (this *UpdateIPPopupAction) RunGet(params struct {
this.Data["item"] = maps.Map{
"id": item.Id,
"value": item.Value,
"ipFrom": item.IpFrom,
"ipTo": item.IpTo,
"expiredAt": item.ExpiredAt,
@@ -48,8 +50,7 @@ func (this *UpdateIPPopupAction) RunGet(params struct {
func (this *UpdateIPPopupAction) RunPost(params struct {
ItemId int64
IpFrom string
IpTo string
Value string
ExpiredAt int64
Reason string
Type string
@@ -61,47 +62,25 @@ func (this *UpdateIPPopupAction) RunPost(params struct {
// 日志
defer this.CreateLogInfo(codes.IPItem_LogUpdateIPItem, params.ItemId)
// TODO 校验ItemId所属用户
switch params.Type {
case "ipv4":
case "ip":
// 校验IP格式
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入开始IP")
Field("value", params.Value).
Require("请输入IP或IP段")
// 校验IP格式ipFrom/ipTo
var ipFromLong uint64
if !utils.IsIPv4(params.IpFrom) {
this.Fail("请输入正确的开始IP")
}
ipFromLong = utils.IP2Long(params.IpFrom)
var ipToLong uint64
if len(params.IpTo) > 0 && !utils.IsIPv4(params.IpTo) {
this.Fail("请输入正确的结束IP")
}
ipToLong = utils.IP2Long(params.IpTo)
if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong {
params.IpTo, params.IpFrom = params.IpFrom, params.IpTo
}
case "ipv6":
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入IP")
// 校验IP格式ipFrom
if !utils.IsIPv6(params.IpFrom) {
this.Fail("请输入正确的IPv6地址")
_, _, _, ok := utils.ParseIPValue(params.Value)
if !ok {
this.FailField("value", "请输入正确的IP格式")
return
}
case "all":
params.IpFrom = "0.0.0.0"
params.Value = "0.0.0.0"
}
_, err := this.RPC().IPItemRPC().UpdateIPItem(this.AdminContext(), &pb.UpdateIPItemRequest{
IpItemId: params.ItemId,
IpFrom: params.IpFrom,
IpTo: params.IpTo,
Value: params.Value,
ExpiredAt: params.ExpiredAt,
Reason: params.Reason,
Type: params.Type,

View File

@@ -24,6 +24,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
this.ErrorPage(err)

View File

@@ -21,6 +21,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
// 网站分组设置
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,

View File

@@ -28,6 +28,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
// 服务分组设置
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,

View File

@@ -1,6 +1,7 @@
package charset
import ( "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
@@ -21,6 +22,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
// 服务分组设置
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,

View File

@@ -23,6 +23,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
this.Data["hasGroupConfig"] = false
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)

View File

@@ -25,6 +25,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
// 服务分组设置
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,

View File

@@ -22,6 +22,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
this.ErrorPage(err)

View File

@@ -21,6 +21,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
// 服务分组设置
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,

View File

@@ -28,6 +28,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
server, _, isOk := serverutils.FindServer(this.Parent(), params.ServerId)
if !isOk {
return

View File

@@ -29,6 +29,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
server, _, isOk := serverutils.FindServer(this.Parent(), params.ServerId)
if !isOk {
return

View File

@@ -21,6 +21,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
this.ErrorPage(err)

View File

@@ -39,7 +39,7 @@ func (this *IndexAction) RunGet(params struct {
}
if firewallPolicy != nil {
// captcha action
var captchaOptions = firewallconfigs.DefaultHTTPFirewallCaptchaAction()
var captchaOptions = firewallconfigs.NewHTTPFirewallCaptchaAction()
if len(firewallPolicy.CaptchaOptionsJSON) > 0 {
err = json.Unmarshal(firewallPolicy.CaptchaOptionsJSON, captchaOptions)
if err != nil {

View File

@@ -0,0 +1,58 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package origins
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"net"
"net/url"
"strings"
"time"
)
type DetectHTTPSAction struct {
actionutils.ParentAction
}
func (this *DetectHTTPSAction) RunPost(params struct {
Addr string
}) {
this.Data["isOk"] = false
// parse from url
if strings.HasPrefix(params.Addr, "http://") || strings.HasPrefix(params.Addr, "https://") {
u, err := url.Parse(params.Addr)
if err == nil {
params.Addr = u.Host
}
}
this.Data["addr"] = params.Addr
if len(params.Addr) == 0 {
this.Success()
return
}
var realHost = params.Addr
host, port, err := net.SplitHostPort(params.Addr)
if err == nil {
if port != "80" {
this.Success()
return
}
realHost = host
}
conn, err := net.DialTimeout("tcp", configutils.QuoteIP(realHost)+":443", 3*time.Second)
if err != nil {
this.Success()
return
}
_ = conn.Close()
this.Data["isOk"] = true
this.Success()
}

View File

@@ -14,6 +14,8 @@ func init() {
GetPost("/addPopup", new(AddPopupAction)).
Post("/delete", new(DeleteAction)).
GetPost("/updatePopup", new(UpdatePopupAction)).
Post("/updateIsOn", new(UpdateIsOnAction)).
Post("/detectHTTPS", new(DetectHTTPSAction)).
EndAll()
})
}

View File

@@ -0,0 +1,31 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package origins
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 {
OriginId int64
IsOn bool
}) {
defer this.CreateLogInfo(codes.ServerOrigin_LogUpdateOriginIsOn, params.OriginId)
_, err := this.RPC().OriginRPC().UpdateOriginIsOn(this.AdminContext(), &pb.UpdateOriginIsOnRequest{
OriginId: params.OriginId,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -24,6 +24,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
// 分组设置
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,

View File

@@ -20,6 +20,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
this.ErrorPage(err)

View File

@@ -23,6 +23,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
this.Data["serverId"] = params.ServerId
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)

View File

@@ -26,6 +26,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
// 服务分组设置
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,

View File

@@ -23,6 +23,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
// 服务分组设置
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,

View File

@@ -65,7 +65,6 @@ func (this *UpdateSchedulingPopupAction) RunGet(params struct {
var isHTTPFamily = false
var isTCPFamily = false
var isUDPFamily = false
var isUnixFamily = false
if params.ServerId > 0 {
_, serverConfig, isOk := serverutils.FindServer(this.Parent(), params.ServerId)
if !isOk {
@@ -74,7 +73,6 @@ func (this *UpdateSchedulingPopupAction) RunGet(params struct {
isHTTPFamily = serverConfig.IsHTTPFamily()
isTCPFamily = serverConfig.IsTCPFamily()
isUDPFamily = serverConfig.IsUDPFamily()
isUnixFamily = serverConfig.IsUnixFamily()
} else {
switch params.Family {
case "http":
@@ -83,8 +81,6 @@ func (this *UpdateSchedulingPopupAction) RunGet(params struct {
isTCPFamily = true
case "udp":
isUDPFamily = true
case "unix":
isUnixFamily = true
}
}
@@ -98,8 +94,7 @@ func (this *UpdateSchedulingPopupAction) RunGet(params struct {
}
if (isHTTPFamily && lists.Contains(networks, "http")) ||
(isTCPFamily && lists.Contains(networks, "tcp")) ||
(isUDPFamily && lists.Contains(networks, "udp")) ||
(isUnixFamily && lists.Contains(networks, "unix")) {
(isUDPFamily && lists.Contains(networks, "udp")) {
schedulingTypes = append(schedulingTypes, m)
}
}

View File

@@ -17,6 +17,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
this.ErrorPage(err)

View File

@@ -1,22 +0,0 @@
package unix
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "index")
this.SecondMenu("unix")
}
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// TODO
this.Show()
}

View File

@@ -1,19 +0,0 @@
package unix
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/serverutils"
"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)).
Helper(serverutils.NewServerHelper()).
Prefix("/servers/server/settings/unix").
Get("", new(IndexAction)).
EndAll()
})
}

View File

@@ -23,6 +23,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
this.Data["serverId"] = params.ServerId
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)

View File

@@ -24,6 +24,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
// 服务分组设置
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,
@@ -52,7 +57,7 @@ func (this *IndexAction) RunGet(params struct {
}
if firewallPolicy != nil {
// captcha action
var captchaOptions = firewallconfigs.DefaultHTTPFirewallCaptchaAction()
var captchaOptions = firewallconfigs.NewHTTPFirewallCaptchaAction()
if len(firewallPolicy.CaptchaOptionsJSON) > 0 {
err = json.Unmarshal(firewallPolicy.CaptchaOptionsJSON, captchaOptions)
if err != nil {

View File

@@ -109,6 +109,7 @@ func (this *AllowListAction) RunGet(params struct {
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"value": item.Value,
"ipFrom": item.IpFrom,
"ipTo": item.IpTo,
"createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt),

View File

@@ -63,9 +63,9 @@ func (this *DenyListAction) RunGet(params struct {
this.ErrorPage(err)
return
}
itemMaps := []maps.Map{}
var itemMaps = []maps.Map{}
for _, item := range itemsResp.IpItems {
expiredTime := ""
var expiredTime = ""
if item.ExpiredAt > 0 {
expiredTime = timeutil.FormatTime("Y-m-d H:i:s", item.ExpiredAt)
}
@@ -109,6 +109,7 @@ func (this *DenyListAction) RunGet(params struct {
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"value": item.Value,
"ipFrom": item.IpFrom,
"ipTo": item.IpTo,
"createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt),

View File

@@ -70,6 +70,7 @@ func (this *TestAction) RunPost(params struct {
if resp.IpItem != nil {
resultMap["item"] = maps.Map{
"id": resp.IpItem.Id,
"value": resp.IpItem.Value,
"ipFrom": resp.IpItem.IpFrom,
"ipTo": resp.IpItem.IpTo,
"reason": resp.IpItem.Reason,

View File

@@ -1,6 +1,7 @@
package ipadmin
import ( "github.com/TeaOSLab/EdgeAdmin/internal/utils"
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
@@ -32,6 +33,7 @@ func (this *UpdateIPPopupAction) RunGet(params struct {
this.Data["item"] = maps.Map{
"id": item.Id,
"value": item.Value,
"ipFrom": item.IpFrom,
"ipTo": item.IpTo,
"expiredAt": item.ExpiredAt,
@@ -48,8 +50,7 @@ func (this *UpdateIPPopupAction) RunGet(params struct {
func (this *UpdateIPPopupAction) RunPost(params struct {
ItemId int64
IpFrom string
IpTo string
Value string
ExpiredAt int64
Reason string
Type string
@@ -61,47 +62,25 @@ func (this *UpdateIPPopupAction) RunPost(params struct {
// 日志
defer this.CreateLogInfo(codes.IPItem_LogUpdateIPItem, params.ItemId)
// TODO 校验ItemId所属用户
switch params.Type {
case "ipv4":
case "ip":
// 校验IP格式
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入开始IP")
Field("value", params.Value).
Require("请输入IP或IP段")
// 校验IP格式ipFrom/ipTo
var ipFromLong uint64
if !utils.IsIPv4(params.IpFrom) {
this.Fail("请输入正确的开始IP")
}
ipFromLong = utils.IP2Long(params.IpFrom)
var ipToLong uint64
if len(params.IpTo) > 0 && !utils.IsIPv4(params.IpTo) {
this.Fail("请输入正确的结束IP")
}
ipToLong = utils.IP2Long(params.IpTo)
if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong {
params.IpTo, params.IpFrom = params.IpFrom, params.IpTo
}
case "ipv6":
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入IP")
// 校验IP格式ipFrom
if !utils.IsIPv6(params.IpFrom) {
this.Fail("请输入正确的IPv6地址")
_, _, _, ok := utils.ParseIPValue(params.Value)
if !ok {
this.FailField("value", "请输入正确的IP格式")
return
}
case "all":
params.IpFrom = "0.0.0.0"
params.Value = "0.0.0.0"
}
_, err := this.RPC().IPItemRPC().UpdateIPItem(this.AdminContext(), &pb.UpdateIPItemRequest{
IpItemId: params.ItemId,
IpFrom: params.IpFrom,
IpTo: params.IpTo,
Value: params.Value,
ExpiredAt: params.ExpiredAt,
Reason: params.Reason,
Type: params.Type,

View File

@@ -21,6 +21,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
// 分组设置
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,

View File

@@ -25,6 +25,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
// 服务分组设置
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,

View File

@@ -23,6 +23,11 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
// 只有HTTP服务才支持
if this.FilterHTTPFamily() {
return
}
// 服务分组设置
groupResp, err := this.RPC().ServerGroupRPC().FindEnabledServerGroupConfigInfo(this.AdminContext(), &pb.FindEnabledServerGroupConfigInfoRequest{
ServerId: params.ServerId,

View File

@@ -50,7 +50,7 @@ func (this *ServerHelper) createLeftMenu(action *actions.ActionObject) {
if serverId == 0 {
return
}
serverIdString := strconv.FormatInt(serverId, 10)
var serverIdString = strconv.FormatInt(serverId, 10)
action.Data["serverId"] = serverId
// 读取server信息
@@ -95,13 +95,11 @@ func (this *ServerHelper) createLeftMenu(action *actions.ActionObject) {
}
// 协议簇
family := ""
var family = ""
if serverConfig.IsHTTPFamily() {
family = "http"
} else if serverConfig.IsTCPFamily() {
family = "tcp"
} else if serverConfig.IsUnixFamily() {
family = "unix"
} else if serverConfig.IsUDPFamily() {
family = "udp"
}
@@ -462,18 +460,6 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
"isActive": secondMenuItem == "reverseProxy",
"isOn": serverConfig.ReverseProxyRef != nil && serverConfig.ReverseProxyRef.IsOn,
})
} else if serverConfig.IsUnixFamily() {
menuItems = append(menuItems, maps.Map{
"name": this.Lang(actionPtr, codes.Server_MenuSettingDNS),
"url": "/servers/server/settings/dns?serverId=" + serverIdString,
"isActive": secondMenuItem == "dns",
})
menuItems = append(menuItems, maps.Map{
"name": this.Lang(actionPtr, codes.Server_MenuSettingUnix),
"url": "/servers/server/settings/unix?serverId=" + serverIdString,
"isActive": secondMenuItem == "unix",
"isOn": serverConfig.Unix != nil && serverConfig.Unix.IsOn && len(serverConfig.Unix.Listen) > 0,
})
} else if serverConfig.IsUDPFamily() {
menuItems = append(menuItems, maps.Map{
"name": this.Lang(actionPtr, codes.Server_MenuSettingDNS),

View File

@@ -0,0 +1,18 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package security
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
)
type DismissXFFPromptAction struct {
actionutils.ParentAction
}
func (this *DismissXFFPromptAction) RunPost(params struct{}) {
helpers.DisableXFFPrompt()
this.Success()
}

View File

@@ -19,7 +19,11 @@ func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
func (this *IndexAction) RunGet(params struct {
ShowAll bool
}) {
this.Data["showAll"] = params.ShowAll
config, err := configloaders.LoadSecurityConfig()
if err != nil {
this.ErrorPage(err)
@@ -66,6 +70,7 @@ func (this *IndexAction) RunGet(params struct{}) {
this.Data["provinces"] = provinceMaps
this.Data["config"] = config
this.Show()
}
@@ -78,6 +83,7 @@ func (this *IndexAction) RunPost(params struct {
AllowRememberLogin bool
ClientIPHeaderNames string
ClientIPHeaderOnly bool
DenySearchEngines bool
DenySpiders bool
@@ -141,6 +147,7 @@ func (this *IndexAction) RunPost(params struct {
// 客户端IP获取方式
config.ClientIPHeaderNames = params.ClientIPHeaderNames
config.ClientIPHeaderOnly = params.ClientIPHeaderOnly
// 禁止搜索引擎和爬虫
config.DenySearchEngines = params.DenySearchEngines

View File

@@ -14,6 +14,7 @@ func init() {
Helper(settingutils.NewHelper("security")).
Prefix("/settings/security").
GetPost("", new(IndexAction)).
Post("/dismissXFFPrompt", new(DismissXFFPromptAction)).
EndAll()
})
}

View File

@@ -55,6 +55,7 @@ func (this *IndexAction) RunPost(params struct {
LogoFile *actions.File
DefaultPageSize int
TimeZone string
DnsResolverType string
SupportModuleCDN bool
SupportModuleNS bool
@@ -85,6 +86,7 @@ func (this *IndexAction) RunPost(params struct {
config.ShowVersion = params.ShowVersion
config.Version = params.Version
config.TimeZone = params.TimeZone
config.DNSResolver.Type = params.DnsResolverType
if params.DefaultPageSize > 0 {
config.DefaultPageSize = params.DefaultPageSize
@@ -111,10 +113,10 @@ func (this *IndexAction) RunPost(params struct {
this.ErrorPage(err)
return
}
fileId := createResp.FileId
var fileId = createResp.FileId
// 上传内容
buf := make([]byte, 512*1024)
var buf = make([]byte, 512*1024)
reader, err := params.FaviconFile.OriginFile.Open()
if err != nil {
this.ErrorPage(err)
@@ -158,10 +160,10 @@ func (this *IndexAction) RunPost(params struct {
this.ErrorPage(err)
return
}
fileId := createResp.FileId
var fileId = createResp.FileId
// 上传内容
buf := make([]byte, 512*1024)
var buf = make([]byte, 512*1024)
reader, err := params.LogoFile.OriginFile.Open()
if err != nil {
this.ErrorPage(err)

View File

@@ -344,10 +344,12 @@ func (this *MySQLInstaller) InstallFromFile(xzFilePath string, targetDir string)
// waiting for startup
for i := 0; i < 30; i++ {
_, err = net.Dial("tcp", "127.0.0.1:3306")
var conn net.Conn
conn, err = net.Dial("tcp", "127.0.0.1:3306")
if err != nil {
time.Sleep(1 * time.Second)
} else {
_ = conn.Close()
break
}
}

View File

@@ -2,10 +2,11 @@ package users
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/otputils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/skip2/go-qrcode"
"github.com/xlzd/gotp"
)
@@ -19,7 +20,8 @@ func (this *OtpQrcodeAction) Init() {
}
func (this *OtpQrcodeAction) RunGet(params struct {
UserId int64
UserId int64
Download bool
}) {
loginResp, err := this.RPC().LoginRPC().FindEnabledLogin(this.AdminContext(), &pb.FindEnabledLoginRequest{
UserId: params.UserId,
@@ -29,19 +31,19 @@ func (this *OtpQrcodeAction) RunGet(params struct {
this.ErrorPage(err)
return
}
login := loginResp.Login
var login = loginResp.Login
if login == nil || !login.IsOn {
this.NotFound("userLogin", params.UserId)
return
}
loginParams := maps.Map{}
var loginParams = maps.Map{}
err = json.Unmarshal(login.ParamsJSON, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
secret := loginParams.GetString("secret")
var secret = loginParams.GetString("secret")
// 当前用户信息
userResp, err := this.RPC().UserRPC().FindEnabledUser(this.AdminContext(), &pb.FindEnabledUserRequest{UserId: params.UserId})
@@ -55,21 +57,22 @@ func (this *OtpQrcodeAction) RunGet(params struct {
return
}
uiConfig, err := configloaders.LoadAdminUIConfig()
productName, err := this.findProductName()
if err != nil {
this.ErrorPage(err)
return
}
var productName = uiConfig.ProductName
if len(productName) == 0 {
productName = "GoEdge用户"
}
var url = gotp.NewDefaultTOTP(secret).ProvisioningUri(user.Username, productName)
data, err := qrcode.Encode(url, qrcode.Medium, 256)
data, err := qrcode.Encode(otputils.FixIssuer(url), qrcode.Medium, 256)
if err != nil {
this.ErrorPage(err)
return
}
if params.Download {
var filename = "OTP-USER-" + user.Username + ".png"
this.AddHeader("Content-Disposition", "attachment; filename=\""+filename+"\";")
}
this.AddHeader("Content-Type", "image/png")
this.AddHeader("Content-Length", types.String(len(data)))
_, _ = this.Write(data)
}

View File

@@ -0,0 +1,18 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package users
import "github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
func (this *OtpQrcodeAction) findProductName() (string, error) {
uiConfig, err := configloaders.LoadAdminUIConfig()
if err != nil {
return "", err
}
var productName = uiConfig.ProductName
if len(productName) == 0 {
productName = "GoEdge用户"
}
return productName, nil
}

View File

@@ -49,6 +49,9 @@ var nodeLogsType = ""
// IP名单
var countUnreadIPItems int64 = 0
// 安全相关
var securityXFFPromptDisabled = false
func init() {
events.On(events.EventStart, func() {
// 节点日志数量
@@ -200,22 +203,37 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
}
// 检查区域
if securityConfig != nil && securityConfig.CheckClientRegion {
var oldClientIP = session.GetString("@ip")
var currentClientIP = loginutils.RemoteIP(action)
if len(oldClientIP) > 0 && len(currentClientIP) > 0 && oldClientIP != currentClientIP {
var oldRegion = loginutils.LookupIPRegion(oldClientIP)
var newRegion = loginutils.LookupIPRegion(currentClientIP)
if newRegion != oldRegion {
var oldClientIP = session.GetString("@ip")
var currentClientIP = loginutils.RemoteIP(action)
if len(oldClientIP) > 0 && len(currentClientIP) > 0 && oldClientIP != currentClientIP {
var oldRegion = loginutils.LookupIPRegion(oldClientIP)
var newRegion = loginutils.LookupIPRegion(currentClientIP)
if newRegion != oldRegion {
if securityConfig != nil && securityConfig.CheckClientRegion {
loginutils.UnsetCookie(action)
session.Delete()
this.login(action)
return false
} else {
// TODO 考虑IP变化时也需要验证主要是考虑被反向代理的情形
action.RedirectURL("/login/validate?from=" + url.QueryEscape(action.Request.URL.String()))
return false
}
}
}
// 是否正在使用反向代理模式
if action.Request.Method == http.MethodGet {
action.Data["teaXFFPrompt"] = false
if !securityXFFPromptDisabled &&
(len(action.Header("X-Forwarded-For")) > 0 || len(action.Header("X-Real-Ip")) > 0 || len(action.Header("Cf-Connecting-Ip")) > 0) &&
securityConfig != nil &&
len(securityConfig.ClientIPHeaderNames) == 0 {
action.Data["teaXFFPrompt"] = true
}
}
// 检查用户是否存在
if !configloaders.CheckAdmin(adminId) {
loginutils.UnsetCookie(action)

View File

@@ -60,12 +60,13 @@ func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramN
}
// StoreAdmin 存储用户名到SESSION
func (this *UserShouldAuth) StoreAdmin(adminId int64, remember bool) {
func (this *UserShouldAuth) StoreAdmin(adminId int64, remember bool, localSid string) {
loginutils.SetCookie(this.action, remember)
var session = this.action.Session()
session.Write("adminId", numberutils.FormatInt64(adminId))
session.Write("@fingerprint", loginutils.CalculateClientFingerprint(this.action))
session.Write("@ip", loginutils.RemoteIP(this.action))
session.Write("@localSid", localSid)
}
func (this *UserShouldAuth) IsUser() bool {

View File

@@ -29,6 +29,11 @@ func init() {
})
}
// DisableXFFPrompt 停用XFF提示
func DisableXFFPrompt() {
securityXFFPromptDisabled = true
}
// 检查用户IP并支持缓存
func checkIP(config *systemconfigs.SecurityConfig, ipAddr string) bool {
ipCacheLocker.Lock()

View File

@@ -29,6 +29,7 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/tasks"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/log"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/login"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/logout"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/messages"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes"
@@ -101,7 +102,6 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/tcp"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/tls"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/udp"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/unix"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/userAgent"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/waf"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/web"

View File

@@ -2366,7 +2366,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
</td>
</tr>
</table>
</div>`}),Vue.component("http-firewall-checkpoint-cc",{props:["v-checkpoint"],data:function(){let e=[],t=60,i=1e3,n=!1,s=!0,o={},a=(null==(o=null!=window.parent.UPDATING_RULE?window.parent.UPDATING_RULE.checkpointOptions:o)&&(o={}),0==(e=null!=o.keys?o.keys:e).length&&(e=["${remoteAddr}","${requestPath}"]),null!=o.period&&(t=o.period),null!=o.threshold&&(i=o.threshold),null!=o.ignoreCommonFiles&&"boolean"==typeof o.ignoreCommonFiles&&(n=o.ignoreCommonFiles),null!=o.enableFingerprint&&"boolean"==typeof o.enableFingerprint&&(s=o.enableFingerprint),this);return setTimeout(function(){a.change()},100),{keys:e,period:t,threshold:i,ignoreCommonFiles:n,enableFingerprint:s,options:{},value:i}},watch:{period:function(){this.change()},threshold:function(){this.change()},ignoreCommonFiles:function(){this.change()},enableFingerprint:function(){this.change()}},methods:{changeKeys:function(e){this.keys=e,this.change()},change:function(){let e=parseInt(this.period.toString()),t=((isNaN(e)||e<=0)&&(e=60),parseInt(this.threshold.toString())),i=((isNaN(t)||t<=0)&&(t=1e3),this.value=t,this.ignoreCommonFiles),n=("boolean"!=typeof i&&(i=!1),this.enableFingerprint);"boolean"!=typeof n&&(n=!0),this.vCheckpoint.options=[{code:"keys",value:this.keys},{code:"period",value:e},{code:"threshold",value:t},{code:"ignoreCommonFiles",value:i},{code:"enableFingerprint",value:n}]},thresholdTooLow:function(){let e=parseInt(this.threshold.toString());return 0<(e=isNaN(e)||e<=0?1e3:e)&&e<5}},template:`<div>
</div>`}),Vue.component("http-firewall-checkpoint-cc",{props:["v-checkpoint"],data:function(){let e=[],t=60,i=1e3,n=!0,s=!0,o={},a=(null==(o=null!=window.parent.UPDATING_RULE?window.parent.UPDATING_RULE.checkpointOptions:o)&&(o={}),0==(e=null!=o.keys?o.keys:e).length&&(e=["${remoteAddr}","${requestPath}"]),null!=o.period&&(t=o.period),null!=o.threshold&&(i=o.threshold),null!=o.ignoreCommonFiles&&"boolean"==typeof o.ignoreCommonFiles&&(n=o.ignoreCommonFiles),null!=o.enableFingerprint&&"boolean"==typeof o.enableFingerprint&&(s=o.enableFingerprint),this);return setTimeout(function(){a.change()},100),{keys:e,period:t,threshold:i,ignoreCommonFiles:n,enableFingerprint:s,options:{},value:i}},watch:{period:function(){this.change()},threshold:function(){this.change()},ignoreCommonFiles:function(){this.change()},enableFingerprint:function(){this.change()}},methods:{changeKeys:function(e){this.keys=e,this.change()},change:function(){let e=parseInt(this.period.toString()),t=((isNaN(e)||e<=0)&&(e=60),parseInt(this.threshold.toString())),i=((isNaN(t)||t<=0)&&(t=1e3),this.value=t,this.ignoreCommonFiles),n=("boolean"!=typeof i&&(i=!1),this.enableFingerprint);"boolean"!=typeof n&&(n=!0),this.vCheckpoint.options=[{code:"keys",value:this.keys},{code:"period",value:e},{code:"threshold",value:t},{code:"ignoreCommonFiles",value:i},{code:"enableFingerprint",value:n}]},thresholdTooLow:function(){let e=parseInt(this.threshold.toString());return 0<(e=isNaN(e)||e<=0?1e3:e)&&e<5}},template:`<div>
<input type="hidden" name="operator" value="gt"/>
<input type="hidden" name="value" :value="value"/>
<table class="ui table">
@@ -2400,10 +2400,10 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
</td>
</tr>
<tr>
<td>忽略常文件</td>
<td>忽略常文件</td>
<td>
<checkbox v-model="ignoreCommonFiles"></checkbox>
<p class="comment">忽略js、css、jpg等常在网页里被引用的文件名。</p>
<p class="comment">忽略js、css、jpg等常在网页里被引用的文件名,即对这些文件的访问不加入计数,可以减少误判几率。</p>
</td>
</tr>
</table>
@@ -2511,22 +2511,22 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
<button class="ui button tiny" @click.prevent="addRef(false)" type="button">+添加缓存条件</button> &nbsp; &nbsp; <a href="" @click.prevent="addRef(true)" style="font-size: 0.8em">+添加不缓存条件</a>
</div>
<div class="margin"></div>
</div>`}),Vue.component("origin-list-box",{props:["v-primary-origins","v-backup-origins","v-server-type","v-params"],data:function(){return{primaryOrigins:this.vPrimaryOrigins,backupOrigins:this.vBackupOrigins}},methods:{createPrimaryOrigin:function(){teaweb.popup("/servers/server/settings/origins/addPopup?originType=primary&"+this.vParams,{width:"45em",height:"27em",callback:function(e){teaweb.success("保存成功",function(){window.location.reload()})}})},createBackupOrigin:function(){teaweb.popup("/servers/server/settings/origins/addPopup?originType=backup&"+this.vParams,{width:"45em",height:"27em",callback:function(e){teaweb.success("保存成功",function(){window.location.reload()})}})},updateOrigin:function(e,t){teaweb.popup("/servers/server/settings/origins/updatePopup?originType="+t+"&"+this.vParams+"&originId="+e,{width:"45em",height:"27em",callback:function(e){teaweb.success("保存成功",function(){window.location.reload()})}})},deleteOrigin:function(e,t){let i=this;teaweb.confirm("确定要删除此源站吗?",function(){Tea.action("/servers/server/settings/origins/delete?"+i.vParams+"&originId="+e+"&originType="+t).post().success(function(){teaweb.success("删除成功",function(){window.location.reload()})})})}},template:`<div>
</div>`}),Vue.component("origin-list-box",{props:["v-primary-origins","v-backup-origins","v-server-type","v-params"],data:function(){return{primaryOrigins:this.vPrimaryOrigins,backupOrigins:this.vBackupOrigins}},methods:{createPrimaryOrigin:function(){teaweb.popup("/servers/server/settings/origins/addPopup?originType=primary&"+this.vParams,{width:"45em",height:"27em",callback:function(e){teaweb.success("保存成功",function(){window.location.reload()})}})},createBackupOrigin:function(){teaweb.popup("/servers/server/settings/origins/addPopup?originType=backup&"+this.vParams,{width:"45em",height:"27em",callback:function(e){teaweb.success("保存成功",function(){window.location.reload()})}})},updateOrigin:function(e,t){teaweb.popup("/servers/server/settings/origins/updatePopup?originType="+t+"&"+this.vParams+"&originId="+e,{width:"45em",height:"27em",callback:function(e){teaweb.success("保存成功",function(){window.location.reload()})}})},deleteOrigin:function(e,t,i){let n=this;teaweb.confirm("确定要删除此源站"+t+"吗?",function(){Tea.action("/servers/server/settings/origins/delete?"+n.vParams+"&originId="+e+"&originType="+i).post().success(function(){teaweb.success("删除成功",function(){window.location.reload()})})})},updateOriginIsOn:function(e,t,i){let n,s,o=(s=i?(n="确定要启用此源站("+t+")吗?","启用成功"):(n="确定要停用此源站("+t+")吗?","停用成功"),this);teaweb.confirm(n,function(){Tea.action("/servers/server/settings/origins/updateIsOn?"+o.vParams+"&originId="+e+"&isOn="+(i?1:0)).post().success(function(){teaweb.success(s,function(){window.location.reload()})})})}},template:`<div>
<h3>主要源站 <a href="" @click.prevent="createPrimaryOrigin()">[添加主要源站]</a> </h3>
<p class="comment" v-if="primaryOrigins.length == 0">暂时还没有主要源站。</p>
<origin-list-table v-if="primaryOrigins.length > 0" :v-origins="vPrimaryOrigins" :v-origin-type="'primary'" @deleteOrigin="deleteOrigin" @updateOrigin="updateOrigin"></origin-list-table>
<origin-list-table v-if="primaryOrigins.length > 0" :v-origins="vPrimaryOrigins" :v-origin-type="'primary'" @deleteOrigin="deleteOrigin" @updateOrigin="updateOrigin" @updateOriginIsOn="updateOriginIsOn"></origin-list-table>
<h3>备用源站 <a href="" @click.prevent="createBackupOrigin()">[添加备用源站]</a></h3>
<p class="comment" v-if="backupOrigins.length == 0" :v-origins="primaryOrigins">暂时还没有备用源站。</p>
<origin-list-table v-if="backupOrigins.length > 0" :v-origins="backupOrigins" :v-origin-type="'backup'" @deleteOrigin="deleteOrigin" @updateOrigin="updateOrigin"></origin-list-table>
</div>`}),Vue.component("origin-list-table",{props:["v-origins","v-origin-type"],data:function(){let t=!1,e=this.vOrigins;return null!=e&&0<e.length&&e.forEach(function(e){null!=e.domains&&0<e.domains.length&&(t=!0)}),{hasMatchedDomains:t}},methods:{deleteOrigin:function(e){this.$emit("deleteOrigin",e,this.vOriginType)},updateOrigin:function(e){this.$emit("updateOrigin",e,this.vOriginType)}},template:`
<p class="comment" v-if="backupOrigins.length == 0">暂时还没有备用源站。</p>
<origin-list-table v-if="backupOrigins.length > 0" :v-origins="backupOrigins" :v-origin-type="'backup'" @deleteOrigin="deleteOrigin" @updateOrigin="updateOrigin" @updateOriginIsOn="updateOriginIsOn"></origin-list-table>
</div>`}),Vue.component("origin-list-table",{props:["v-origins","v-origin-type"],data:function(){let t=!1,e=this.vOrigins;return null!=e&&0<e.length&&e.forEach(function(e){null!=e.domains&&0<e.domains.length&&(t=!0)}),{hasMatchedDomains:t}},methods:{deleteOrigin:function(e,t){this.$emit("deleteOrigin",e,t,this.vOriginType)},updateOrigin:function(e){this.$emit("updateOrigin",e,this.vOriginType)},updateOriginIsOn:function(e,t,i){this.$emit("updateOriginIsOn",e,t,i)}},template:`
<table class="ui table selectable">
<thead>
<tr>
<th>源站地址</th>
<th>权重</th>
<th class="width10">状态</th>
<th class="two op">操作</th>
<th class="width5">权重</th>
<th class="width6">状态</th>
<th class="three op">操作</th>
</tr>
</thead>
<tbody>
@@ -2534,15 +2534,15 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
<td :class="{disabled:!origin.isOn}">
<a href="" @click.prevent="updateOrigin(origin.id)" :class="{disabled:!origin.isOn}">{{origin.addr}} &nbsp;<i class="icon expand small"></i></a>
<div style="margin-top: 0.3em">
<tiny-basic-label v-if="origin.isOSS"><i class="icon hdd outline"></i>对象存储</tiny-basic-label>
<tiny-basic-label v-if="origin.name.length > 0">{{origin.name}}</tiny-basic-label>
<tiny-basic-label v-if="origin.hasCert">证书</tiny-basic-label>
<tiny-basic-label v-if="origin.host != null && origin.host.length > 0">主机名: {{origin.host}}</tiny-basic-label>
<tiny-basic-label v-if="origin.followPort">端口跟随</tiny-basic-label>
<tiny-basic-label v-if="origin.addr != null && origin.addr.startsWith('https://') && origin.http2Enabled">HTTP/2</tiny-basic-label>
<tiny-basic-label class="grey border-grey" v-if="origin.isOSS"><i class="icon hdd outline"></i>对象存储</tiny-basic-label>
<tiny-basic-label class="grey border-grey" v-if="origin.name.length > 0">{{origin.name}}</tiny-basic-label>
<tiny-basic-label class="grey border-grey" v-if="origin.hasCert">证书</tiny-basic-label>
<tiny-basic-label class="grey border-grey" v-if="origin.host != null && origin.host.length > 0">主机名: {{origin.host}}</tiny-basic-label>
<tiny-basic-label class="grey border-grey" v-if="origin.followPort">端口跟随</tiny-basic-label>
<tiny-basic-label class="grey border-grey" v-if="origin.addr != null && origin.addr.startsWith('https://') && origin.http2Enabled">HTTP/2</tiny-basic-label>
<span v-if="origin.domains != null && origin.domains.length > 0"><tiny-basic-label v-for="domain in origin.domains">匹配: {{domain}}</tiny-basic-label></span>
<span v-else-if="hasMatchedDomains"><tiny-basic-label>匹配: 所有域名</tiny-basic-label></span>
<span v-if="origin.domains != null && origin.domains.length > 0"><tiny-basic-label class="grey border-grey" v-for="domain in origin.domains">匹配: {{domain}}</tiny-basic-label></span>
<span v-else-if="hasMatchedDomains"><tiny-basic-label class="grey border-grey">匹配: 所有域名</tiny-basic-label></span>
</div>
</td>
<td :class="{disabled:!origin.isOn}">{{origin.weight}}</td>
@@ -2551,7 +2551,8 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
</td>
<td>
<a href="" @click.prevent="updateOrigin(origin.id)">修改</a> &nbsp;
<a href="" @click.prevent="deleteOrigin(origin.id)">删除</a>
<a href="" v-if="origin.isOn" @click.prevent="updateOriginIsOn(origin.id, origin.addr, false)">停用</a><a href="" v-if="!origin.isOn" @click.prevent="updateOriginIsOn(origin.id, origin.addr, true)"><span class="red">启用</span></a> &nbsp;
<a href="" @click.prevent="deleteOrigin(origin.id, origin.addr)">删除</a>
</td>
</tr>
</tbody>
@@ -2934,9 +2935,8 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
<td>匹配条件</td>
<td>
<http-request-conds-box :v-conds="config.conds" @change="changeConds"></http-request-conds-box>
</td>
</td>
</tr>
</tr>
</tbody>
</table>
<div class="margin"></div>
@@ -3034,7 +3034,7 @@ example2.com
</div>
<div class="ui divider"></div>
<button type="button" class="ui button tiny" @click.prevent="addProvince">修改</button> &nbsp; <button type="button" class="ui button tiny" v-show="provinces.length > 0" @click.prevent="resetProvinces">清空</button>
</div>`}),Vue.component("http-referers-config-box",{props:["v-referers-config","v-is-location","v-is-group"],data:function(){let e=this.vReferersConfig;return null==(e=null==e?{isPrior:!1,isOn:!1,allowEmpty:!0,allowSameDomain:!0,allowDomains:[],denyDomains:[],checkOrigin:!0}:e).allowDomains&&(e.allowDomains=[]),null==e.denyDomains&&(e.denyDomains=[]),{config:e}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.config.isPrior)&&this.config.isOn},changeAllowDomains:function(e){"object"==typeof e&&(this.config.allowDomains=e,this.$forceUpdate())},changeDenyDomains:function(e){"object"==typeof e&&(this.config.denyDomains=e,this.$forceUpdate())}},template:`<div>
</div>`}),Vue.component("http-referers-config-box",{props:["v-referers-config","v-is-location","v-is-group"],data:function(){let e=this.vReferersConfig;return null==(e=null==e?{isPrior:!1,isOn:!1,allowEmpty:!0,allowSameDomain:!0,allowDomains:[],denyDomains:[],checkOrigin:!0}:e).allowDomains&&(e.allowDomains=[]),null==e.denyDomains&&(e.denyDomains=[]),{config:e,moreOptionsVisible:!1}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.config.isPrior)&&this.config.isOn},changeAllowDomains:function(e){"object"==typeof e&&(this.config.allowDomains=e,this.$forceUpdate())},changeDenyDomains:function(e){"object"==typeof e&&(this.config.denyDomains=e,this.$forceUpdate())},showMoreOptions:function(){this.moreOptionsVisible=!this.moreOptionsVisible}},template:`<div>
<input type="hidden" name="referersJSON" :value="JSON.stringify(config)"/>
<table class="ui table selectable definition">
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
@@ -3079,6 +3079,11 @@ example2.com
<p class="comment">禁止的来源域名列表,比如<code-label>example.org</code-label>、<code-label>*.example.org</code-label>;除了这些禁止的来源域名外,其他域名都会被允许,除非限定了允许的来源域名。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator @change="showMoreOptions"></more-options-indicator></td>
</tr>
</tbody>
<tbody v-show="moreOptionsVisible && isOn()">
<tr>
<td>同时检查Origin</td>
<td>
@@ -3086,6 +3091,20 @@ example2.com
<p class="comment">如果请求没有指定Referer Header则尝试检查Origin Header多用于跨站调用。</p>
</td>
</tr>
<tr>
<td>例外URL</td>
<td>
<url-patterns-box v-model="config.exceptURLPatterns"></url-patterns-box>
<p class="comment">如果填写了例外URL表示这些URL跳过不做处理。</p>
</td>
</tr>
<tr>
<td>限制URL</td>
<td>
<url-patterns-box v-model="config.onlyURLPatterns"></url-patterns-box>
<p class="comment">如果填写了限制URL表示只对这些URL进行处理如果不填则表示支持所有的URL。</p>
</td>
</tr>
</tbody>
</table>
<div class="ui margin"></div>
@@ -3971,7 +3990,54 @@ example2.com
</tr>
</table>
</div>
`}),Vue.component("http-compression-config-box",{props:["v-compression-config","v-is-location","v-is-group"],mounted:function(){let e=this;sortLoad(function(){e.initSortableTypes()})},data:function(){let t=this.vCompressionConfig,e=(null==(t=null==t?{isPrior:!1,isOn:!1,useDefaultTypes:!0,types:["brotli","gzip","zstd","deflate"],level:5,decompressData:!1,gzipRef:null,deflateRef:null,brotliRef:null,minLength:{count:1,unit:"kb"},maxLength:{count:32,unit:"mb"},mimeTypes:["text/*","application/javascript","application/json","application/atom+xml","application/rss+xml","application/xhtml+xml","font/*","image/svg+xml"],extensions:[".js",".json",".html",".htm",".xml",".css",".woff2",".txt"],exceptExtensions:[".apk",".ipa"],conds:null,enablePartialContent:!1}:t).types&&(t.types=[]),null==t.mimeTypes&&(t.mimeTypes=[]),null==t.extensions&&(t.extensions=[]),[{name:"Gzip",code:"gzip",isOn:!0},{name:"Deflate",code:"deflate",isOn:!0},{name:"Brotli",code:"brotli",isOn:!0},{name:"ZSTD",code:"zstd",isOn:!0}]),i=[];return t.types.forEach(function(t){e.forEach(function(e){t==e.code&&(e.isOn=!0,i.push(e))})}),e.forEach(function(e){t.types.$contains(e.code)||(e.isOn=!1,i.push(e))}),{config:t,moreOptionsVisible:!1,allTypes:i}},watch:{"config.level":function(e){let t=parseInt(e);isNaN(t)||t<1?t=1:10<t&&(t=10),this.config.level=t}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.config.isPrior)&&this.config.isOn},changeExtensions:function(i){i.forEach(function(e,t){0<e.length&&"."!=e[0]&&(i[t]="."+e)}),this.config.extensions=i},changeExceptExtensions:function(i){i.forEach(function(e,t){0<e.length&&"."!=e[0]&&(i[t]="."+e)}),this.config.exceptExtensions=i},changeMimeTypes:function(e){this.config.mimeTypes=e},changeAdvancedVisible:function(){this.moreOptionsVisible=!this.moreOptionsVisible},changeConds:function(e){this.config.conds=e},changeType:function(){this.config.types=[];let t=this;this.allTypes.forEach(function(e){e.isOn&&t.config.types.push(e.code)})},initSortableTypes:function(){let n=document.querySelector("#compression-types-box"),s=this;Sortable.create(n,{draggable:".checkbox",handle:".icon.handle",onStart:function(){},onUpdate:function(e){let t=n.querySelectorAll(".checkbox"),i=[];t.forEach(function(e){e=e.getAttribute("data-code");i.push(e)}),s.config.types=i}})}},template:`<div>
`}),Vue.component("http-firewall-js-cookie-options",{props:["v-js-cookie-options"],mounted:function(){this.updateSummary()},data:function(){let e=this.vJsCookieOptions;return{options:e=null==e?{life:0,maxFails:0,failBlockTimeout:0,failBlockScopeAll:!1,scope:"service"}:e,isEditing:!1,summary:""}},watch:{"options.life":function(e){let t=parseInt(e,10);isNaN(t)&&(t=0),this.options.life=t,this.updateSummary()},"options.maxFails":function(e){let t=parseInt(e,10);isNaN(t)&&(t=0),this.options.maxFails=t,this.updateSummary()},"options.failBlockTimeout":function(e){let t=parseInt(e,10);isNaN(t)&&(t=0),this.options.failBlockTimeout=t,this.updateSummary()},"options.failBlockScopeAll":function(e){this.updateSummary()}},methods:{edit:function(){this.isEditing=!this.isEditing},updateSummary:function(){let e=[];0<this.options.life&&e.push("有效时间"+this.options.life+"秒"),0<this.options.maxFails&&e.push("最多失败"+this.options.maxFails+"次"),0<this.options.failBlockTimeout&&e.push("失败拦截"+this.options.failBlockTimeout+"秒"),this.options.failBlockScopeAll&&e.push("尝试全局封禁"),0==e.length?this.summary="默认配置":this.summary=e.join(" / ")},confirm:function(){this.isEditing=!1}},template:`<div>
<input type="hidden" name="jsCookieOptionsJSON" :value="JSON.stringify(options)"/>
<a href="" @click.prevent="edit">{{summary}} <i class="icon angle" :class="{up: isEditing, down: !isEditing}"></i></a>
<div v-show="isEditing" style="margin-top: 0.5em">
<table class="ui table definition selectable">
<tbody>
<tr>
<td class="title">有效时间</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="9" v-model="options.life" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span>
</div>
<p class="comment">验证通过后在这个时间内不再验证默认3600秒。</p>
</td>
</tr>
<tr>
<td>最多失败次数</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="9" v-model="options.maxFails" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">次</span>
</div>
<p class="comment"><span v-if="options.maxFails > 0 && options.maxFails < 5" class="red">建议填入一个不小于5的数字以减少误判几率。</span>允许用户失败尝试的最多次数超过这个次数将被自动加入黑名单。如果为空或者为0表示不限制。</p>
</td>
</tr>
<tr>
<td>失败拦截时间</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="9" v-model="options.failBlockTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span>
</div>
<p class="comment">在达到最多失败次数大于0自动拦截的时长如果为0表示不自动拦截。</p>
</td>
</tr>
<tr>
<td>失败全局封禁</td>
<td>
<checkbox v-model="options.failBlockScopeAll"></checkbox>
<p class="comment">选中后表示允许系统尝试全局封禁某个IP以提升封禁性能。</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
`}),Vue.component("http-compression-config-box",{props:["v-compression-config","v-is-location","v-is-group"],mounted:function(){let e=this;sortLoad(function(){e.initSortableTypes()})},data:function(){let t=this.vCompressionConfig,e=(null==(t=null==t?{isPrior:!1,isOn:!1,useDefaultTypes:!0,types:["brotli","gzip","zstd","deflate"],level:0,decompressData:!1,gzipRef:null,deflateRef:null,brotliRef:null,minLength:{count:1,unit:"kb"},maxLength:{count:32,unit:"mb"},mimeTypes:["text/*","application/javascript","application/json","application/atom+xml","application/rss+xml","application/xhtml+xml","font/*","image/svg+xml"],extensions:[".js",".json",".html",".htm",".xml",".css",".woff2",".txt"],exceptExtensions:[".apk",".ipa"],conds:null,enablePartialContent:!1,onlyURLPatterns:[],exceptURLPatterns:[]}:t).types&&(t.types=[]),null==t.mimeTypes&&(t.mimeTypes=[]),null==t.extensions&&(t.extensions=[]),[{name:"Gzip",code:"gzip",isOn:!0},{name:"Deflate",code:"deflate",isOn:!0},{name:"Brotli",code:"brotli",isOn:!0},{name:"ZSTD",code:"zstd",isOn:!0}]),i=[];return t.types.forEach(function(t){e.forEach(function(e){t==e.code&&(e.isOn=!0,i.push(e))})}),e.forEach(function(e){t.types.$contains(e.code)||(e.isOn=!1,i.push(e))}),{config:t,moreOptionsVisible:!1,allTypes:i}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.config.isPrior)&&this.config.isOn},changeExtensions:function(i){i.forEach(function(e,t){0<e.length&&"."!=e[0]&&(i[t]="."+e)}),this.config.extensions=i},changeExceptExtensions:function(i){i.forEach(function(e,t){0<e.length&&"."!=e[0]&&(i[t]="."+e)}),this.config.exceptExtensions=i},changeMimeTypes:function(e){this.config.mimeTypes=e},changeAdvancedVisible:function(){this.moreOptionsVisible=!this.moreOptionsVisible},changeConds:function(e){this.config.conds=e},changeType:function(){this.config.types=[];let t=this;this.allTypes.forEach(function(e){e.isOn&&t.config.types.push(e.code)})},initSortableTypes:function(){let n=document.querySelector("#compression-types-box"),s=this;Sortable.create(n,{draggable:".checkbox",handle:".icon.handle",onStart:function(){},onUpdate:function(e){let t=n.querySelectorAll(".checkbox"),i=[];t.forEach(function(e){e=e.getAttribute("data-code");i.push(e)}),s.config.types=i}})}},template:`<div>
<input type="hidden" name="compressionJSON" :value="JSON.stringify(config)"/>
<table class="ui table definition selectable">
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
@@ -3987,15 +4053,6 @@ example2.com
</tr>
</tbody>
<tbody v-show="isOn()">
<tr>
<td>压缩级别</td>
<td>
<select class="ui dropdown auto-width" v-model="config.level">
<option v-for="i in 10" :value="i">{{i}}</option>
</select>
<p class="comment">级别越高,压缩比例越大。</p>
</td>
</tr>
<tr>
<td>支持的扩展名</td>
<td>
@@ -4068,17 +4125,31 @@ example2.com
<checkbox v-model="config.enablePartialContent"></checkbox>
<p class="comment">支持对分片内容PartialContent的压缩除非客户端有特殊要求一般不需要启用。</p>
</td>
</tr>
<tr>
<td>例外URL</td>
<td>
<url-patterns-box v-model="config.exceptURLPatterns"></url-patterns-box>
<p class="comment">如果填写了例外URL表示这些URL跳过不做处理。</p>
</td>
</tr>
<tr>
<td>限制URL</td>
<td>
<url-patterns-box v-model="config.onlyURLPatterns"></url-patterns-box>
<p class="comment">如果填写了限制URL表示只对这些URL进行压缩处理如果不填则表示支持所有的URL。</p>
</td>
</tr>
<tr>
<td>匹配条件</td>
<td>
<http-request-conds-box :v-conds="config.conds" @change="changeConds"></http-request-conds-box>
</td>
</td>
</tr>
</tbody>
</table>
<div class="margin"></div>
</div>`}),Vue.component("http-cc-config-box",{props:["v-cc-config","v-is-location","v-is-group"],data:function(){let e=this.vCcConfig;return null!=(e=null==e?{isPrior:!1,isOn:!1,enableFingerprint:!0,enableGET302:!0,onlyURLPatterns:[],exceptURLPatterns:[],useDefaultThresholds:!0}:e).thresholds&&0!=e.thresholds.length||(e.thresholds=[{maxRequests:0},{maxRequests:0},{maxRequests:0}]),"boolean"!=typeof e.enableFingerprint&&(e.enableFingerprint=!0),"boolean"!=typeof e.enableGET302&&(e.enableGET302=!0),null==e.onlyURLPatterns&&(e.onlyURLPatterns=[]),null==e.exceptURLPatterns&&(e.exceptURLPatterns=[]),{config:e,moreOptionsVisible:!1,minQPSPerIP:e.minQPSPerIP,useCustomThresholds:!e.useDefaultThresholds,thresholdMaxRequests0:this.maxRequestsStringAtThresholdIndex(e,0),thresholdMaxRequests1:this.maxRequestsStringAtThresholdIndex(e,1),thresholdMaxRequests2:this.maxRequestsStringAtThresholdIndex(e,2)}},watch:{minQPSPerIP:function(e){let t=parseInt(e.toString());(isNaN(t)||t<0)&&(t=0),this.config.minQPSPerIP=t},thresholdMaxRequests0:function(e){this.setThresholdMaxRequests(0,e)},thresholdMaxRequests1:function(e){this.setThresholdMaxRequests(1,e)},thresholdMaxRequests2:function(e){this.setThresholdMaxRequests(2,e)},useCustomThresholds:function(e){this.config.useDefaultThresholds=!e}},methods:{maxRequestsStringAtThresholdIndex:function(t,i){if(null==t.thresholds)return"";if(i<t.thresholds.length){let e=t.thresholds[i].maxRequests.toString();return e="0"==e?"":e}return""},setThresholdMaxRequests:function(e,t){let i=parseInt(t);(isNaN(i)||i<0)&&(i=0),e<this.config.thresholds.length&&(this.config.thresholds[e].maxRequests=i)},showMoreOptions:function(){this.moreOptionsVisible=!this.moreOptionsVisible}},template:`<div>
</div>`}),Vue.component("http-cc-config-box",{props:["v-cc-config","v-is-location","v-is-group"],data:function(){let e=this.vCcConfig;return null!=(e=null==e?{isPrior:!1,isOn:!1,enableFingerprint:!0,enableGET302:!0,onlyURLPatterns:[],exceptURLPatterns:[],useDefaultThresholds:!0,ignoreCommonFiles:!0}:e).thresholds&&0!=e.thresholds.length||(e.thresholds=[{maxRequests:0},{maxRequests:0},{maxRequests:0}]),"boolean"!=typeof e.enableFingerprint&&(e.enableFingerprint=!0),"boolean"!=typeof e.enableGET302&&(e.enableGET302=!0),null==e.onlyURLPatterns&&(e.onlyURLPatterns=[]),null==e.exceptURLPatterns&&(e.exceptURLPatterns=[]),{config:e,moreOptionsVisible:!1,minQPSPerIP:e.minQPSPerIP,useCustomThresholds:!e.useDefaultThresholds,thresholdMaxRequests0:this.maxRequestsStringAtThresholdIndex(e,0),thresholdMaxRequests1:this.maxRequestsStringAtThresholdIndex(e,1),thresholdMaxRequests2:this.maxRequestsStringAtThresholdIndex(e,2)}},watch:{minQPSPerIP:function(e){let t=parseInt(e.toString());(isNaN(t)||t<0)&&(t=0),this.config.minQPSPerIP=t},thresholdMaxRequests0:function(e){this.setThresholdMaxRequests(0,e)},thresholdMaxRequests1:function(e){this.setThresholdMaxRequests(1,e)},thresholdMaxRequests2:function(e){this.setThresholdMaxRequests(2,e)},useCustomThresholds:function(e){this.config.useDefaultThresholds=!e}},methods:{maxRequestsStringAtThresholdIndex:function(t,i){if(null==t.thresholds)return"";if(i<t.thresholds.length){let e=t.thresholds[i].maxRequests.toString();return e="0"==e?"":e}return""},setThresholdMaxRequests:function(e,t){let i=parseInt(t);(isNaN(i)||i<0)&&(i=0),e<this.config.thresholds.length&&(this.config.thresholds[e].maxRequests=i)},showMoreOptions:function(){this.moreOptionsVisible=!this.moreOptionsVisible}},template:`<div>
<input type="hidden" name="ccJSON" :value="JSON.stringify(config)"/>
<table class="ui table definition selectable">
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
@@ -4110,7 +4181,14 @@ example2.com
<url-patterns-box v-model="config.onlyURLPatterns"></url-patterns-box>
<p class="comment">如果填写了限制URL表示只对这些URL进行CC防护处理如果不填则表示支持所有的URL。</p>
</td>
</tr>
</tr>
<tr>
<td>忽略常用文件</td>
<td>
<checkbox v-model="config.ignoreCommonFiles"></checkbox>
<p class="comment">忽略js、css、jpg等常在网页里被引用的文件名即对这些文件的访问不加入计数可以减少误判几率。</p>
</td>
</tr>
<tr>
<td>检查请求来源指纹</td>
<td>
@@ -4310,6 +4388,7 @@ example2.com
<span v-if="options == null">默认设置</span>
<div v-else>
状态码:{{options.statusCode}} / 提示内容:<span v-if="options.body != null && options.body.length > 0">[{{options.body.length}}字符]</span><span v-else class="disabled">[无]</span> / 超时时间:{{options.timeout}}秒 <span v-if="options.timeoutMax > options.timeout">/ 最大封禁时长:{{options.timeoutMax}}秒</span>
<span v-if="options.failBlockScopeAll"> / 尝试全局封禁</span>
</div>
</div>
`}),Vue.component("http-access-log-config-box",{props:["v-access-log-config","v-fields","v-default-field-codes","v-is-location","v-is-group"],data:function(){let t=this,i=(setTimeout(function(){t.changeFields()},100),{isPrior:!1,isOn:!1,fields:[1,2,6,7],status1:!0,status2:!0,status3:!0,status4:!0,status5:!0,firewallOnly:!1,enableClientClosed:!1});return null!=this.vAccessLogConfig&&(i=this.vAccessLogConfig),this.vFields.forEach(function(e){null==t.vAccessLogConfig?e.isChecked=t.vDefaultFieldCodes.$contains(e.code):e.isChecked=i.fields.$contains(e.code)}),{accessLog:i,hasRequestBodyField:this.vFields.$contains(8),showAdvancedOptions:!1}},methods:{changeFields:function(){this.accessLog.fields=this.vFields.filter(function(e){return e.isChecked}).map(function(e){return e.code}),this.hasRequestBodyField=this.accessLog.fields.$contains(8)},changeAdvanced:function(e){this.showAdvancedOptions=e}},template:`<div>
@@ -4576,7 +4655,8 @@ example2.com
</tr>
</tbody>
</table>
</div>`}),Vue.component("ssl-certs-view",{props:["v-certs"],data:function(){let e=this.vCerts;return{certs:e=null==e?[]:e}},methods:{formatTime:function(e){return new Date(1e3*e).format("Y-m-d")},viewCert:function(e){teaweb.popup("/servers/certs/certPopup?certId="+e,{height:"28em",width:"48em"})}},template:`<div>
</div>`}),Vue.component("http-firewall-js-cookie-options-viewer",{props:["v-js-cookie-options"],mounted:function(){this.updateSummary()},data:function(){let e=this.vJsCookieOptions;return{options:e=null==e?{life:0,maxFails:0,failBlockTimeout:0,failBlockScopeAll:!1,scope:""}:e,summary:""}},methods:{updateSummary:function(){let e=[];0<this.options.life&&e.push("有效时间"+this.options.life+"秒"),0<this.options.maxFails&&e.push("最多失败"+this.options.maxFails+"次"),0<this.options.failBlockTimeout&&e.push("失败拦截"+this.options.failBlockTimeout+"秒"),this.options.failBlockScopeAll&&e.push("尝试全局封禁"),0==e.length?this.summary="默认配置":this.summary=e.join(" / ")}},template:`<div>{{summary}}</div>
`}),Vue.component("ssl-certs-view",{props:["v-certs"],data:function(){let e=this.vCerts;return{certs:e=null==e?[]:e}},methods:{formatTime:function(e){return new Date(1e3*e).format("Y-m-d")},viewCert:function(e){teaweb.popup("/servers/certs/certPopup?certId="+e,{height:"28em",width:"48em"})}},template:`<div>
<div v-if="certs != null && certs.length > 0">
<div class="ui label small basic" v-for="(cert, index) in certs">
{{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}} &nbsp;<a href="" title="查看" @click.prevent="viewCert(cert.id)"><i class="icon expand blue"></i></a>
@@ -5070,10 +5150,11 @@ example2.com
</td>
</tr>
</table>
</div>`}),Vue.component("http-firewall-block-options",{props:["v-block-options"],data:function(){return{blockOptions:this.vBlockOptions,statusCode:this.vBlockOptions.statusCode,timeout:this.vBlockOptions.timeout,timeoutMax:this.vBlockOptions.timeoutMax,isEditing:!1}},watch:{statusCode:function(e){e=parseInt(e);isNaN(e)?this.blockOptions.statusCode=403:this.blockOptions.statusCode=e},timeout:function(e){e=parseInt(e);isNaN(e)?this.blockOptions.timeout=0:this.blockOptions.timeout=e},timeoutMax:function(e){e=parseInt(e);isNaN(e)?this.blockOptions.timeoutMax=0:this.blockOptions.timeoutMax=e}},methods:{edit:function(){this.isEditing=!this.isEditing}},template:`<div>
<input type="hidden" name="blockOptionsJSON" :value="JSON.stringify(blockOptions)"/>
<a href="" @click.prevent="edit">状态码:{{statusCode}} / 提示内容:<span v-if="blockOptions.body != null && blockOptions.body.length > 0">[{{blockOptions.body.length}}字符]</span><span v-else class="disabled">[无]</span> <span v-if="timeout > 0"> / 封禁时长:{{timeout}}秒</span>
</div>`}),Vue.component("http-firewall-block-options",{props:["v-block-options"],data:function(){return{options:this.vBlockOptions,statusCode:this.vBlockOptions.statusCode,timeout:this.vBlockOptions.timeout,timeoutMax:this.vBlockOptions.timeoutMax,isEditing:!1}},watch:{statusCode:function(e){e=parseInt(e);isNaN(e)?this.options.statusCode=403:this.options.statusCode=e},timeout:function(e){e=parseInt(e);isNaN(e)?this.options.timeout=0:this.options.timeout=e},timeoutMax:function(e){e=parseInt(e);isNaN(e)?this.options.timeoutMax=0:this.options.timeoutMax=e}},methods:{edit:function(){this.isEditing=!this.isEditing}},template:`<div>
<input type="hidden" name="blockOptionsJSON" :value="JSON.stringify(options)"/>
<a href="" @click.prevent="edit">状态码:{{statusCode}} / 提示内容:<span v-if="options.body != null && options.body.length > 0">[{{options.body.length}}字符]</span><span v-else class="disabled">[无]</span> <span v-if="timeout > 0"> / 封禁时长:{{timeout}}秒</span>
<span v-if="timeoutMax > timeout"> / 最大封禁时长:{{timeoutMax}}秒</span>
<span v-if="options.failBlockScopeAll"> / 尝试全局封禁</span>
<i class="icon angle" :class="{up: isEditing, down: !isEditing}"></i></a>
<table class="ui table" v-show="isEditing">
<tr>
@@ -5085,7 +5166,7 @@ example2.com
<tr>
<td>提示内容</td>
<td>
<textarea rows="3" v-model="blockOptions.body"></textarea>
<textarea rows="3" v-model="options.body"></textarea>
</td>
</tr>
<tr>
@@ -5108,6 +5189,13 @@ example2.com
<p class="comment">如果最大封禁时长大于封禁时长({{timeout}}秒),那么表示每次封禁的时候,将会在这两个时长数字之间随机选取一个数字作为最终的封禁时长。</p>
</td>
</tr>
<tr>
<td>失败全局封禁</td>
<td>
<checkbox v-model="options.failBlockScopeAll"></checkbox>
<p class="comment">选中后表示允许系统尝试全局封禁某个IP以提升封禁性能。</p>
</td>
</tr>
</table>
</div>
`}),Vue.component("http-hls-config-box",{props:["value","v-is-location","v-is-group"],data:function(){let e=this.value,t=(e=null==e?{isPrior:!1}:e).encrypting;return null==t&&(t={isOn:!1,onlyURLPatterns:[],exceptURLPatterns:[]},e.encrypting=t),{config:e,encryptingConfig:t,encryptingMoreOptionsVisible:!1}},methods:{isOn:function(){return!this.vIsLocation&&!this.vIsGroup||this.config.isPrior},showEncryptingMoreOptions:function(){this.encryptingMoreOptionsVisible=!this.encryptingMoreOptionsVisible}},template:`<div>
@@ -5618,7 +5706,7 @@ example2.com
</tbody>
</table>
<div class="margin"></div>
</div>`}),Vue.component("http-firewall-captcha-options",{props:["v-captcha-options"],mounted:function(){this.updateSummary()},data:function(){let e=this.vCaptchaOptions;return(e=null==e?{captchaType:"default",countLetters:0,life:0,maxFails:0,failBlockTimeout:0,failBlockScopeAll:!1,uiIsOn:!1,uiTitle:"",uiPrompt:"",uiButtonTitle:"",uiShowRequestId:!0,uiCss:"",uiFooter:"",uiBody:"",cookieId:"",lang:"",geeTestConfig:{isOn:!1,captchaId:"",captchaKey:""}}:e).countLetters<=0&&(e.countLetters=6),null!=e.captchaType&&0!=e.captchaType.length||(e.captchaType="default"),{options:e,isEditing:!1,summary:"",uiBodyWarning:"",captchaTypes:window.WAF_CAPTCHA_TYPES}},watch:{"options.countLetters":function(e){let t=parseInt(e,10);isNaN(t)||t<0?t=0:10<t&&(t=10),this.options.countLetters=t},"options.life":function(e){let t=parseInt(e,10);isNaN(t)&&(t=0),this.options.life=t,this.updateSummary()},"options.maxFails":function(e){let t=parseInt(e,10);isNaN(t)&&(t=0),this.options.maxFails=t,this.updateSummary()},"options.failBlockTimeout":function(e){let t=parseInt(e,10);isNaN(t)&&(t=0),this.options.failBlockTimeout=t,this.updateSummary()},"options.failBlockScopeAll":function(e){this.updateSummary()},"options.captchaType":function(e){this.updateSummary()},"options.uiIsOn":function(e){this.updateSummary()},"options.uiBody":function(e){/<form(>|\s).+\$\{body}.*<\/form>/s.test(e)?this.uiBodyWarning="使<form></form>${body}":this.uiBodyWarning=""},"options.geeTestConfig.isOn":function(e){this.updateSummary()}},methods:{edit:function(){this.isEditing=!this.isEditing},updateSummary:function(){let e=[],i=(0<this.options.life&&e.push(""+this.options.life+""),0<this.options.maxFails&&e.push(""+this.options.maxFails+""),0<this.options.failBlockTimeout&&e.push(""+this.options.failBlockTimeout+""),this.options.failBlockScopeAll&&e.push(""),this);var t=this.captchaTypes.$find(function(e,t){return t.code==i.options.captchaType});null!=t&&e.push(""+t.name),"default"==this.options.captchaType&&this.options.uiIsOn&&e.push("UI"),null!=this.options.geeTestConfig&&this.options.geeTestConfig.isOn&&e.push(""),0==e.length?this.summary="":this.summary=e.join(" / ")},confirm:function(){this.isEditing=!1}},template:`<div>
</div>`}),Vue.component("http-firewall-captcha-options",{props:["v-captcha-options"],mounted:function(){this.updateSummary()},data:function(){let e=this.vCaptchaOptions;return(e=null==e?{captchaType:"default",countLetters:0,life:0,maxFails:0,failBlockTimeout:0,failBlockScopeAll:!1,uiIsOn:!1,uiTitle:"",uiPrompt:"",uiButtonTitle:"",uiShowRequestId:!0,uiCss:"",uiFooter:"",uiBody:"",cookieId:"",lang:"",geeTestConfig:{isOn:!1,captchaId:"",captchaKey:""}}:e).countLetters<=0&&(e.countLetters=6),null!=e.captchaType&&0!=e.captchaType.length||(e.captchaType="default"),{options:e,isEditing:!1,summary:"",uiBodyWarning:"",captchaTypes:window.WAF_CAPTCHA_TYPES}},watch:{"options.countLetters":function(e){let t=parseInt(e,10);isNaN(t)||t<0?t=0:10<t&&(t=10),this.options.countLetters=t},"options.life":function(e){let t=parseInt(e,10);isNaN(t)&&(t=0),this.options.life=t,this.updateSummary()},"options.maxFails":function(e){let t=parseInt(e,10);isNaN(t)&&(t=0),this.options.maxFails=t,this.updateSummary()},"options.failBlockTimeout":function(e){let t=parseInt(e,10);isNaN(t)&&(t=0),this.options.failBlockTimeout=t,this.updateSummary()},"options.failBlockScopeAll":function(e){this.updateSummary()},"options.captchaType":function(e){this.updateSummary()},"options.uiIsOn":function(e){this.updateSummary()},"options.uiBody":function(e){/<form(>|\s).+\$\{body}.*<\/form>/s.test(e)?this.uiBodyWarning="使<form></form>${body}":this.uiBodyWarning=""},"options.geeTestConfig.isOn":function(e){this.updateSummary()}},methods:{edit:function(){this.isEditing=!this.isEditing},updateSummary:function(){let e=[],i=(0<this.options.life&&e.push(""+this.options.life+""),0<this.options.maxFails&&e.push(""+this.options.maxFails+""),0<this.options.failBlockTimeout&&e.push(""+this.options.failBlockTimeout+""),this.options.failBlockScopeAll&&e.push(""),this);var t=this.captchaTypes.$find(function(e,t){return t.code==i.options.captchaType});null!=t&&e.push(""+t.name),"default"==this.options.captchaType&&this.options.uiIsOn&&e.push("UI"),null!=this.options.geeTestConfig&&this.options.geeTestConfig.isOn&&e.push(""),0==e.length?this.summary="":this.summary=e.join(" / ")},confirm:function(){this.isEditing=!1}},template:`<div>
<input type="hidden" name="captchaOptionsJSON" :value="JSON.stringify(options)"/>
<a href="" @click.prevent="edit">{{summary}} <i class="icon angle" :class="{up: isEditing, down: !isEditing}"></i></a>
<div v-show="isEditing" style="margin-top: 0.5em">
@@ -5667,7 +5755,7 @@ example2.com
<td>失败全局封禁</td>
<td>
<checkbox v-model="options.failBlockScopeAll"></checkbox>
<p class="comment">是否在失败时全局封禁默认为只封禁对单个网站的访问</p>
<p class="comment">选中后表示允许系统尝试全局封禁某个IP以提升封禁性能</p>
</td>
</tr>
@@ -5761,7 +5849,7 @@ example2.com
</table>
</div>
</div>
`}),Vue.component("user-agent-config-box",{props:["v-is-location","v-is-group","value"],data:function(){let e=this.value;return null==(e=null==e?{isPrior:!1,isOn:!1,filters:[]}:e).filters&&(e.filters=[]),{config:e,isAdding:!1,addingFilter:{keywords:[],action:"deny"}}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.config.isPrior)&&this.config.isOn},remove:function(e){let t=this;teaweb.confirm("确定要删除此名单吗?",function(){t.config.filters.$remove(e)})},add:function(){this.isAdding=!0},confirm:function(){if("deny"==this.addingFilter.action)this.config.filters.push(this.addingFilter);else{let i=-1;this.config.filters.forEach(function(e,t){"allow"==e.action&&(i=t)}),i<0?this.config.filters.unshift(this.addingFilter):this.config.filters.$insert(i+1,this.addingFilter)}this.cancel()},cancel:function(){this.isAdding=!1,this.addingFilter={keywords:[],action:"deny"}},changeKeywords:function(e){this.addingFilter.keywords=e}},template:`<div>
`}),Vue.component("user-agent-config-box",{props:["v-is-location","v-is-group","value"],data:function(){let e=this.value;return null==(e=null==e?{isPrior:!1,isOn:!1,filters:[]}:e).filters&&(e.filters=[]),{config:e,isAdding:!1,addingFilter:{keywords:[],action:"deny"},moreOptionsVisible:!1,batchKeywords:""}},methods:{isOn:function(){return(!this.vIsLocation&&!this.vIsGroup||this.config.isPrior)&&this.config.isOn},remove:function(e){let t=this;teaweb.confirm("确定要删除此名单吗?",function(){t.config.filters.$remove(e)})},add:function(){this.isAdding=!0;let e=this;setTimeout(function(){e.$refs.batchKeywords.focus()})},confirm:function(){if("deny"==this.addingFilter.action)this.config.filters.push(this.addingFilter);else{let i=-1;this.config.filters.forEach(function(e,t){"allow"==e.action&&(i=t)}),i<0?this.config.filters.unshift(this.addingFilter):this.config.filters.$insert(i+1,this.addingFilter)}this.cancel()},cancel:function(){this.isAdding=!1,this.addingFilter={keywords:[],action:"deny"},this.batchKeywords=""},changeKeywords:function(e){let t=e.split(/\n/),i=[];t.forEach(function(e){e=e.trim(),i.$contains(e)||i.push(e)}),this.addingFilter.keywords=i},showMoreOptions:function(){this.moreOptionsVisible=!this.moreOptionsVisible}},template:`<div>
<input type="hidden" name="userAgentJSON" :value="JSON.stringify(config)"/>
<table class="ui table definition selectable">
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
@@ -5811,8 +5899,8 @@ example2.com
<tr>
<td class="title">UA关键词</td>
<td>
<values-box :v-values="addingFilter.keywords" :v-allow-empty="true" @change="changeKeywords"></values-box>
<p class="comment">不区分大小写比如<code-label>Chrome</code-label><code-label>*</code-label><code-label>*Firefox*</code-label>UserAgent</p>
<textarea v-model="batchKeywords" @input="changeKeywords(batchKeywords)" ref="batchKeywords" style="width: 20em" placeholder="*浏览器标识*"></textarea>
<p class="comment">每行一个关键词不区分大小写比如<code-label>Chrome</code-label><code-label>*</code-label><code-label>*Firefox*</code-label>UserAgent</p>
</td>
</tr>
<tr>
@@ -5831,6 +5919,25 @@ example2.com
</div>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator @change="showMoreOptions"></more-options-indicator></td>
</tr>
</tbody>
<tbody v-show="moreOptionsVisible && isOn()">
<tr>
<td>例外URL</td>
<td>
<url-patterns-box v-model="config.exceptURLPatterns"></url-patterns-box>
<p class="comment">如果填写了例外URL表示这些URL跳过不做处理</p>
</td>
</tr>
<tr>
<td>限制URL</td>
<td>
<url-patterns-box v-model="config.onlyURLPatterns"></url-patterns-box>
<p class="comment">如果填写了限制URL表示只对这些URL进行处理如果不填则表示支持所有的URL</p>
</td>
</tr>
</tbody>
</table>
<div class="margin"></div>
@@ -5976,7 +6083,7 @@ example2.com
<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>
<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>
@@ -6008,8 +6115,12 @@ example2.com
</td>
<td>
<span v-if="item.type != 'all'" :class="{green: item.list != null && item.list.type == 'white'}">
<keyword :v-word="keyword">{{item.ipFrom}}</keyword> <span> <span class="small red" v-if="item.isRead != null && !item.isRead">&nbsp;New&nbsp;</span>&nbsp;<a :href="'/servers/iplists?ip=' + item.ipFrom" v-if="vShowSearchButton" title="IP"><span><i class="icon search small" style="color: #ccc"></i></span></a></span>
<span v-if="item.ipTo.length > 0"> - <keyword :v-word="keyword">{{item.ipTo}}</keyword></span></span>
<span v-if="item.value != null && item.value.length > 0"><keyword :v-word="keyword">{{item.value}}</keyword></span>
<span v-else>
<keyword :v-word="keyword">{{item.ipFrom}}</keyword> <span> <span class="small red" v-if="item.isRead != null && !item.isRead">&nbsp;New&nbsp;</span>&nbsp;<a :href="'/servers/iplists?ip=' + item.ipFrom" v-if="vShowSearchButton" title="IP"><span><i class="icon search small" style="color: #ccc"></i></span></a></span>
<span v-if="item.ipTo.length > 0"> - <keyword :v-word="keyword">{{item.ipTo}}</keyword></span>
</span>
</span>
<span v-else class="disabled">*</span>
<div v-if="item.region != null && item.region.length > 0">
@@ -6028,8 +6139,8 @@ example2.com
<span v-if="item.policy.id > 0">
<span v-if="item.policy.server != null">
<a :href="'/servers/server/settings/waf/ipadmin/allowList?serverId=' + item.policy.server.id + '&firewallPolicyId=' + item.policy.id" v-if="item.list.type == 'white'">[服务{{item.policy.server.name}}]</a>
<a :href="'/servers/server/settings/waf/ipadmin/denyList?serverId=' + item.policy.server.id + '&firewallPolicyId=' + item.policy.id" v-if="item.list.type == 'black'">[服务{{item.policy.server.name}}]</a>
<a :href="'/servers/server/settings/waf/ipadmin/allowList?serverId=' + item.policy.server.id + '&firewallPolicyId=' + item.policy.id" v-if="item.list.type == 'white'">[网站{{item.policy.server.name}}]</a>
<a :href="'/servers/server/settings/waf/ipadmin/denyList?serverId=' + item.policy.server.id + '&firewallPolicyId=' + item.policy.id" v-if="item.list.type == 'black'">[网站{{item.policy.server.name}}]</a>
</span>
<span v-else>
<a :href="'/servers/components/waf/ipadmin/lists?firewallPolicyId=' + item.policy.id + '&type=' + item.list.type">[策略{{item.policy.name}}]</a>
@@ -6052,11 +6163,12 @@ example2.com
<td>
<div v-if="item.expiredTime.length > 0">
{{item.expiredTime}}
<div v-if="item.isExpired" style="margin-top: 0.5em">
<div v-if="item.isExpired && item.lifeSeconds == null" style="margin-top: 0.5em">
<span class="ui label tiny basic red">已过期</span>
</div>
<div v-if="item.lifeSeconds != null && item.lifeSeconds > 0">
<span class="small grey">{{formatSeconds(item.lifeSeconds)}}</span>
<div v-if="item.lifeSeconds != null">
<span class="small grey" v-if="item.lifeSeconds > 0">{{formatSeconds(item.lifeSeconds)}}</span>
<span class="small red" v-if="item.lifeSeconds < 0">已过期</span>
</div>
</div>
<span v-else class="disabled">不过期</span>
@@ -6086,11 +6198,13 @@ example2.com
</table>
</div>`}),Vue.component("ip-item-text",{props:["v-item"],template:`<span>
<span v-if="vItem.type == 'all'">*</span>
<span v-if="vItem.type == 'ipv4' || vItem.type.length == 0">
{{vItem.ipFrom}}
<span v-if="vItem.ipTo.length > 0">- {{vItem.ipTo}}</span>
</span>
<span v-if="vItem.type == 'ipv6'">{{vItem.ipFrom}}</span>
<span v-else>
<span v-if="vItem.value != null && vItem.value.length > 0">{{vItem.value}}</span>
<span v-else>
{{vItem.ipFrom}}
<span v-if="vItem.ipTo != null &&vItem.ipTo.length > 0">- {{vItem.ipTo}}</span>
</span>
</span>
<span v-if="vItem.eventLevelName != null && vItem.eventLevelName.length > 0">&nbsp; 级别{{vItem.eventLevelName}}</span>
</span>`}),Vue.component("ip-box",{props:["v-ip"],methods:{popup:function(){let e=this.vIp;var t;null!=e&&0!=e.length||(t=this.$refs.container,null==(e=t.innerText)&&(e=t.textContent)),teaweb.popup("/servers/ipbox?ip="+e,{width:"50em",height:"30em"})}},template:'<span @click.prevent="popup()" ref="container"><slot></slot></span>'}),Vue.component("sms-sender",{props:["value","name"],mounted:function(){this.initType(this.config.type)},data:function(){let e=this.value;return{config:e=null==e?{isOn:!1,type:"webHook",webHookParams:{url:"",method:"POST"}}:e}},watch:{"config.type":function(e){this.initType(e)}},methods:{initType:function(e){"webHook"===e&&null==this.config.webHookParams&&(this.config.webHookParams={url:"",method:"POST"})},test:function(){window.TESTING_SMS_CONFIG=this.config,teaweb.popup("/users/setting/smsTest",{height:"22em"})}},template:`<div>
<input type="hidden" :name="name" :value="JSON.stringify(config)"/>
@@ -6287,7 +6401,7 @@ example2.com
<button class="ui button tiny" type="button" @click.prevent="create()">+</button>
</div>
</div>
</div>`}),Vue.component("datetime-input",{props:["v-name","v-timestamp"],mounted:function(){let t=this;teaweb.datepicker(this.$refs.dayInput,function(e){t.day=e,t.hour="23",t.minute="59",t.second="59",t.change()})},data:function(){let t=this.vTimestamp,i=(null!=t?(t=parseInt(t),isNaN(t)&&(t=0)):t=0,""),n="",s="",o="";if(0<t){let e=new Date;e.setTime(1e3*t);var a=e.getFullYear().toString(),l=this.leadingZero((e.getMonth()+1).toString(),2);i=a+"-"+l+"-"+this.leadingZero(e.getDate().toString(),2),n=this.leadingZero(e.getHours().toString(),2),s=this.leadingZero(e.getMinutes().toString(),2),o=this.leadingZero(e.getSeconds().toString(),2)}return{timestamp:t,day:i,hour:n,minute:s,second:o,hasDayError:!1,hasHourError:!1,hasMinuteError:!1,hasSecondError:!1}},methods:{change:function(){if(/^\d{4}-\d{1,2}-\d{1,2}$/.test(this.day)){var t=this.day.split("-"),i=parseInt(t[0]),n=parseInt(t[1]);if(n<1||12<n)this.hasDayError=!0;else{t=parseInt(t[2]);if(t<1||32<t)this.hasDayError=!0;else if(this.hasDayError=!1,/^\d+$/.test(this.hour)){var s=parseInt(this.hour);if(isNaN(s))this.hasHourError=!0;else if(s<0||24<=s)this.hasHourError=!0;else if(this.hasHourError=!1,/^\d+$/.test(this.minute)){var o=parseInt(this.minute);if(isNaN(o))this.hasMinuteError=!0;else if(o<0||60<=o)this.hasMinuteError=!0;else if(this.hasMinuteError=!1,/^\d+$/.test(this.second)){var a=parseInt(this.second);if(isNaN(a))this.hasSecondError=!0;else if(a<0||60<=a)this.hasSecondError=!0;else{this.hasSecondError=!1;let e=new Date(i,n-1,t,s,o,a);this.timestamp=Math.floor(e.getTime()/1e3)}}else this.hasSecondError=!0}else this.hasMinuteError=!0}else this.hasHourError=!0}}else this.hasDayError=!0},leadingZero:function(t,i){if(i<=(t=t.toString()).length)return t;for(let e=0;e<i-t.length;e++)t="0"+t;return t},resultTimestamp:function(){return this.timestamp},nextDays:function(e){let t=new Date;t.setTime(t.getTime()+86400*e*1e3),this.day=t.getFullYear()+"-"+this.leadingZero(t.getMonth()+1,2)+"-"+this.leadingZero(t.getDate(),2),this.hour=this.leadingZero(t.getHours(),2),this.minute=this.leadingZero(t.getMinutes(),2),this.second=this.leadingZero(t.getSeconds(),2),this.change()},nextHours:function(e){let t=new Date;t.setTime(t.getTime()+3600*e*1e3),this.day=t.getFullYear()+"-"+this.leadingZero(t.getMonth()+1,2)+"-"+this.leadingZero(t.getDate(),2),this.hour=this.leadingZero(t.getHours(),2),this.minute=this.leadingZero(t.getMinutes(),2),this.second=this.leadingZero(t.getSeconds(),2),this.change()}},template:`<div>
</div>`}),Vue.component("datetime-input",{props:["v-name","v-timestamp"],mounted:function(){let t=this;teaweb.datepicker(this.$refs.dayInput,function(e){t.day=e,t.hour="23",t.minute="59",t.second="59",t.change()})},data:function(){let t=this.vTimestamp,i=(null!=t?(t=parseInt(t),isNaN(t)&&(t=0)):t=0,""),n="",s="",o="";if(0<t){let e=new Date;e.setTime(1e3*t);var a=e.getFullYear().toString(),l=this.leadingZero((e.getMonth()+1).toString(),2);i=a+"-"+l+"-"+this.leadingZero(e.getDate().toString(),2),n=this.leadingZero(e.getHours().toString(),2),s=this.leadingZero(e.getMinutes().toString(),2),o=this.leadingZero(e.getSeconds().toString(),2)}return{timestamp:t,day:i,hour:n,minute:s,second:o,hasDayError:!1,hasHourError:!1,hasMinuteError:!1,hasSecondError:!1}},methods:{change:function(){if(/^\d{4}-\d{1,2}-\d{1,2}$/.test(this.day)){var t=this.day.split("-"),i=parseInt(t[0]),n=parseInt(t[1]);if(n<1||12<n)this.hasDayError=!0;else{t=parseInt(t[2]);if(t<1||32<t)this.hasDayError=!0;else if(this.hasDayError=!1,/^\d+$/.test(this.hour)){var s=parseInt(this.hour);if(isNaN(s))this.hasHourError=!0;else if(s<0||24<=s)this.hasHourError=!0;else if(this.hasHourError=!1,/^\d+$/.test(this.minute)){var o=parseInt(this.minute);if(isNaN(o))this.hasMinuteError=!0;else if(o<0||60<=o)this.hasMinuteError=!0;else if(this.hasMinuteError=!1,/^\d+$/.test(this.second)){var a=parseInt(this.second);if(isNaN(a))this.hasSecondError=!0;else if(a<0||60<=a)this.hasSecondError=!0;else{this.hasSecondError=!1;let e=new Date(i,n-1,t,s,o,a);this.timestamp=Math.floor(e.getTime()/1e3)}}else this.hasSecondError=!0}else this.hasMinuteError=!0}else this.hasHourError=!0}}else this.hasDayError=!0},leadingZero:function(t,i){if(i<=(t=t.toString()).length)return t;for(let e=0;e<i-t.length;e++)t="0"+t;return t},resultTimestamp:function(){return this.timestamp},nextYear:function(){let e=new Date;e.setFullYear(e.getFullYear()+1),this.day=e.getFullYear()+"-"+this.leadingZero(e.getMonth()+1,2)+"-"+this.leadingZero(e.getDate(),2),this.hour=this.leadingZero(e.getHours(),2),this.minute=this.leadingZero(e.getMinutes(),2),this.second=this.leadingZero(e.getSeconds(),2),this.change()},nextDays:function(e){let t=new Date;t.setTime(t.getTime()+86400*e*1e3),this.day=t.getFullYear()+"-"+this.leadingZero(t.getMonth()+1,2)+"-"+this.leadingZero(t.getDate(),2),this.hour=this.leadingZero(t.getHours(),2),this.minute=this.leadingZero(t.getMinutes(),2),this.second=this.leadingZero(t.getSeconds(),2),this.change()},nextHours:function(e){let t=new Date;t.setTime(t.getTime()+3600*e*1e3),this.day=t.getFullYear()+"-"+this.leadingZero(t.getMonth()+1,2)+"-"+this.leadingZero(t.getDate(),2),this.hour=this.leadingZero(t.getHours(),2),this.minute=this.leadingZero(t.getMinutes(),2),this.second=this.leadingZero(t.getSeconds(),2),this.change()}},template:`<div>
<input type="hidden" :name="vName" :value="timestamp"/>
<div class="ui fields inline" style="padding: 0; margin:0">
<div class="ui field" :class="{error: hasDayError}">
@@ -6299,7 +6413,7 @@ example2.com
<div class="ui field">:</div>
<div class="ui field" :class="{error: hasSecondError}"><input type="text" v-model="second" maxlength="2" style="width:4em" placeholder="" @input="change"/></div>
</div>
<p class="comment">常用时间:<a href="" @click.prevent="nextHours(1)"> &nbsp;1小时&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(1)"> &nbsp;1天&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(3)"> &nbsp;3天&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(7)"> &nbsp;1周&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(30)"> &nbsp;30天&nbsp; </a> </p>
<p class="comment">常用时间:<a href="" @click.prevent="nextHours(1)"> &nbsp;1小时&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(1)"> &nbsp;1天&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(3)"> &nbsp;3天&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(7)"> &nbsp;1周&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(30)"> &nbsp;30天&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextYear()"> &nbsp;1年&nbsp; </a> </p>
</div>`}),Vue.component("label-on",{props:["v-is-on"],template:'<div><span v-if="vIsOn" class="green">已启用</span><span v-if="!vIsOn" class="red">已停用</span></div>'}),Vue.component("code-label",{methods:{click:function(e){this.$emit("click",e)}},template:'<span class="ui label basic small" style="padding: 3px;margin-left:2px;margin-right:2px" @click.prevent="click"><slot></slot></span>'}),Vue.component("code-label-plain",{template:'<span class="ui label basic tiny" style="padding: 3px;margin-left:2px;margin-right:2px"><slot></slot></span>'}),Vue.component("tiny-label",{template:'<span class="ui label tiny" style="margin-bottom: 0.5em"><slot></slot></span>'}),Vue.component("tiny-basic-label",{template:'<span class="ui label tiny basic" style="margin-bottom: 0.5em"><slot></slot></span>'}),Vue.component("micro-basic-label",{template:'<span class="ui label tiny basic" style="margin-bottom: 0.5em; font-size: 0.7em; padding: 4px"><slot></slot></span>'}),Vue.component("grey-label",{props:["color"],data:function(){let e="grey";return{labelColor:e=null!=this.color&&0<this.color.length?"red":e}},template:'<span class="ui label basic tiny" :class="labelColor" style="margin-top: 0.4em; font-size: 0.7em; border: 1px solid #ddd!important; font-weight: normal;"><slot></slot></span>'}),Vue.component("optional-label",{template:'<em><span class="grey">(可选)</span></em>'}),Vue.component("plus-label",{template:'<span style="color: #B18701;">Plus专属功能。</span>'}),Vue.component("pro-warning-label",{template:'<span><i class="icon warning circle yellow"></i>注意:通常不需要修改;如要修改,请在专家指导下进行。</span>'}),Vue.component("js-page",{props:["v-max"],data:function(){let e=this.vMax;return{max:e=null==e?0:e,page:1}},methods:{updateMax:function(e){this.max=e},selectPage:function(e){this.page=e,this.$emit("change",e)}},template:`<div>
<div class="page" v-if="max > 1">
<a href="" v-for="i in max" :class="{active: i == page}" @click.prevent="selectPage(i)">{{i}}</a>
@@ -6525,7 +6639,7 @@ example2.com
<div class="ui label tiny basic" v-if="vAddresses != null" v-for="addr in vAddresses">
{{addr.protocol}}://<span v-if="addr.host.length > 0">{{addr.host.quoteIP()}}</span><span v-else>*</span>:{{addr.portRange}}
</div>
</div>`}),Vue.component("url-patterns-box",{props:["value"],data:function(){let e=[];return{patterns:e=null!=this.value?this.value:e,isAdding:!1,addingPattern:{type:"wildcard",pattern:""},editingIndex:-1,patternIsInvalid:!1,windowIsSmall:window.innerWidth<600}},methods:{add:function(){this.isAdding=!0;let e=this;setTimeout(function(){e.$refs.patternInput.focus()})},edit:function(e){this.isAdding=!0,this.editingIndex=e,this.addingPattern={type:this.patterns[e].type,pattern:this.patterns[e].pattern}},confirm:function(){if(0==this.addingPattern.pattern.trim().length){let e=this;void teaweb.warn("请输入URL",function(){e.$refs.patternInput.focus()})}else this.editingIndex<0?this.patterns.push({type:this.addingPattern.type,pattern:this.addingPattern.pattern}):(this.patterns[this.editingIndex].type=this.addingPattern.type,this.patterns[this.editingIndex].pattern=this.addingPattern.pattern),this.notifyChange(),this.cancel()},remove:function(e){this.patterns.$remove(e),this.cancel(),this.notifyChange()},cancel:function(){this.isAdding=!1,this.addingPattern={type:"wildcard",pattern:""},this.editingIndex=-1},patternTypeName:function(e){switch(e){case"wildcard":return"通配符";case"regexp":return"正则"}return""},notifyChange:function(){this.$emit("input",this.patterns)},changePattern:function(){this.patternIsInvalid=!1;let e=this.addingPattern.pattern;switch(this.addingPattern.type){case"wildcard":0<=e.indexOf("?")&&(this.patternIsInvalid=!0);break;case"regexp":if(0<=e.indexOf("?")){var t=e.split("?");for(let e=0;e<t.length-1;e++)0!=t[e].length&&"\\"==t[e][t[e].length-1]||(this.patternIsInvalid=!0)}}}},template:`<div>
</div>`}),Vue.component("url-patterns-box",{props:["value"],data:function(){let e=[];return{patterns:e=null!=this.value?this.value:e,isAdding:!1,addingPattern:{type:"wildcard",pattern:""},editingIndex:-1,patternIsInvalid:!1,windowIsSmall:window.innerWidth<600}},methods:{add:function(){this.isAdding=!0;let e=this;setTimeout(function(){e.$refs.patternInput.focus()})},edit:function(e){this.isAdding=!0,this.editingIndex=e,this.addingPattern={type:this.patterns[e].type,pattern:this.patterns[e].pattern}},confirm:function(){if(this.requireURL(this.addingPattern.type)&&0==this.addingPattern.pattern.trim().length){let e=this;return void teaweb.warn("请输入URL",function(){e.$refs.patternInput.focus()})}this.editingIndex<0?this.patterns.push({type:this.addingPattern.type,pattern:this.addingPattern.pattern}):(this.patterns[this.editingIndex].type=this.addingPattern.type,this.patterns[this.editingIndex].pattern=this.addingPattern.pattern),this.notifyChange(),this.cancel()},remove:function(e){this.patterns.$remove(e),this.cancel(),this.notifyChange()},cancel:function(){this.isAdding=!1,this.addingPattern={type:"wildcard",pattern:""},this.editingIndex=-1},patternTypeName:function(e){switch(e){case"wildcard":return"通配符";case"regexp":return"正则";case"images":return"常见图片文件";case"audios":return"常见音频文件";case"videos":return"常见视频文件"}return""},notifyChange:function(){this.$emit("input",this.patterns)},changePattern:function(){this.patternIsInvalid=!1;let e=this.addingPattern.pattern;switch(this.addingPattern.type){case"wildcard":0<=e.indexOf("?")&&(this.patternIsInvalid=!0);break;case"regexp":if(0<=e.indexOf("?")){var t=e.split("?");for(let e=0;e<t.length-1;e++)0!=t[e].length&&"\\"==t[e][t[e].length-1]||(this.patternIsInvalid=!0)}}},requireURL:function(e){return"wildcard"==e||"regexp"==e}},template:`<div>
<div v-show="patterns.length > 0">
<div v-for="(pattern, index) in patterns" class="ui label basic small" :class="{blue: index == editingIndex, disabled: isAdding && index != editingIndex}" style="margin-bottom: 0.8em">
<span class="grey" style="font-weight: normal">[{{patternTypeName(pattern.type)}}]</span> <span >{{pattern.pattern}}</span> &nbsp;
@@ -6539,14 +6653,17 @@ example2.com
<select class="ui dropdown auto-width" v-model="addingPattern.type">
<option value="wildcard">通配符</option>
<option value="regexp">正则表达式</option>
<option value="images">常见图片</option>
<option value="audios">常见音频</option>
<option value="videos">常见视频</option>
</select>
</div>
<div class="ui field">
<div class="ui field" v-show="addingPattern.type == 'wildcard' || addingPattern.type == 'regexp'">
<input type="text" :placeholder="(addingPattern.type == 'wildcard') ? '可以使用星号(*)通配符,不区分大小写' : '可以使用正则表达式,不区分大小写'" v-model="addingPattern.pattern" @input="changePattern" size="36" ref="patternInput" @keyup.enter="confirm()" @keypress.enter.prevent="1" spellcheck="false"/>
<p class="comment" v-if="patternIsInvalid"><span class="red" style="font-weight: normal"><span v-if="addingPattern.type == 'wildcard'">通配符</span><span v-if="addingPattern.type == 'regexp'">正则表达式</span>中不能包含问号(?)及问号以后的内容。</span></p>
</div>
<div class="ui field" style="padding-left: 0">
<tip-icon content="通配符示例:<br/>单个路径开头:/hello/world/*<br/>单个路径结尾:*/hello/world<br/>包含某个路径:*/article/*<br/>某个域名下的所有URL*example.com/*" v-if="addingPattern.type == 'wildcard'"></tip-icon>
<div class="ui field" style="padding-left: 0" v-show="addingPattern.type == 'wildcard' || addingPattern.type == 'regexp'">
<tip-icon content="通配符示例:<br/>单个路径开头:/hello/world/*<br/>单个路径结尾:*/hello/world<br/>包含某个路径:*/article/*<br/>某个域名下的所有URL*example.com/*<br/>忽略某个扩展名:*.js" v-if="addingPattern.type == 'wildcard'"></tip-icon>
<tip-icon content="正则表达式示例:<br/>单个路径开头:^/hello/world<br/>单个路径结尾:/hello/world$<br/>包含某个路径:/article/<br/>匹配某个数字路径:/article/(\\d+)<br/>某个域名下的所有URL^(http|https)://example.com/" v-if="addingPattern.type == 'regexp'"></tip-icon>
</div>
<div class="ui field">

View File

@@ -6658,7 +6658,7 @@ Vue.component("http-firewall-checkpoint-cc", {
let keys = []
let period = 60
let threshold = 1000
let ignoreCommonFiles = false
let ignoreCommonFiles = true
let enableFingerprint = true
let options = {}
@@ -6809,10 +6809,10 @@ Vue.component("http-firewall-checkpoint-cc", {
</td>
</tr>
<tr>
<td>忽略常文件</td>
<td>忽略常文件</td>
<td>
<checkbox v-model="ignoreCommonFiles"></checkbox>
<p class="comment">忽略js、css、jpg等常在网页里被引用的文件名。</p>
<p class="comment">忽略js、css、jpg等常在网页里被引用的文件名,即对这些文件的访问不加入计数,可以减少误判几率。</p>
</td>
</tr>
</table>
@@ -7374,9 +7374,9 @@ Vue.component("origin-list-box", {
}
})
},
deleteOrigin: function (originId, originType) {
deleteOrigin: function (originId, originAddr, originType) {
let that = this
teaweb.confirm("确定要删除此源站吗?", function () {
teaweb.confirm("确定要删除此源站" + originAddr + "吗?", function () {
Tea.action("/servers/server/settings/origins/delete?" + that.vParams + "&originId=" + originId + "&originType=" + originType)
.post()
.success(function () {
@@ -7385,16 +7385,37 @@ Vue.component("origin-list-box", {
})
})
})
},
updateOriginIsOn: function (originId, originAddr, isOn) {
let message
let resultMessage
if (isOn) {
message = "确定要启用此源站(" + originAddr + ")吗?"
resultMessage = "启用成功"
} else {
message = "确定要停用此源站(" + originAddr + ")吗?"
resultMessage = "停用成功"
}
let that = this
teaweb.confirm(message, function () {
Tea.action("/servers/server/settings/origins/updateIsOn?" + that.vParams + "&originId=" + originId + "&isOn=" + (isOn ? 1 : 0))
.post()
.success(function () {
teaweb.success(resultMessage, function () {
window.location.reload()
})
})
})
}
},
template: `<div>
<h3>主要源站 <a href="" @click.prevent="createPrimaryOrigin()">[添加主要源站]</a> </h3>
<p class="comment" v-if="primaryOrigins.length == 0">暂时还没有主要源站。</p>
<origin-list-table v-if="primaryOrigins.length > 0" :v-origins="vPrimaryOrigins" :v-origin-type="'primary'" @deleteOrigin="deleteOrigin" @updateOrigin="updateOrigin"></origin-list-table>
<origin-list-table v-if="primaryOrigins.length > 0" :v-origins="vPrimaryOrigins" :v-origin-type="'primary'" @deleteOrigin="deleteOrigin" @updateOrigin="updateOrigin" @updateOriginIsOn="updateOriginIsOn"></origin-list-table>
<h3>备用源站 <a href="" @click.prevent="createBackupOrigin()">[添加备用源站]</a></h3>
<p class="comment" v-if="backupOrigins.length == 0" :v-origins="primaryOrigins">暂时还没有备用源站。</p>
<origin-list-table v-if="backupOrigins.length > 0" :v-origins="backupOrigins" :v-origin-type="'backup'" @deleteOrigin="deleteOrigin" @updateOrigin="updateOrigin"></origin-list-table>
<p class="comment" v-if="backupOrigins.length == 0">暂时还没有备用源站。</p>
<origin-list-table v-if="backupOrigins.length > 0" :v-origins="backupOrigins" :v-origin-type="'backup'" @deleteOrigin="deleteOrigin" @updateOrigin="updateOrigin" @updateOriginIsOn="updateOriginIsOn"></origin-list-table>
</div>`
})
@@ -7416,11 +7437,14 @@ Vue.component("origin-list-table", {
}
},
methods: {
deleteOrigin: function (originId) {
this.$emit("deleteOrigin", originId, this.vOriginType)
deleteOrigin: function (originId, originAddr) {
this.$emit("deleteOrigin", originId, originAddr, this.vOriginType)
},
updateOrigin: function (originId) {
this.$emit("updateOrigin", originId, this.vOriginType)
},
updateOriginIsOn: function (originId, originAddr, isOn) {
this.$emit("updateOriginIsOn", originId, originAddr, isOn)
}
},
template: `
@@ -7428,9 +7452,9 @@ Vue.component("origin-list-table", {
<thead>
<tr>
<th>源站地址</th>
<th>权重</th>
<th class="width10">状态</th>
<th class="two op">操作</th>
<th class="width5">权重</th>
<th class="width6">状态</th>
<th class="three op">操作</th>
</tr>
</thead>
<tbody>
@@ -7438,15 +7462,15 @@ Vue.component("origin-list-table", {
<td :class="{disabled:!origin.isOn}">
<a href="" @click.prevent="updateOrigin(origin.id)" :class="{disabled:!origin.isOn}">{{origin.addr}} &nbsp;<i class="icon expand small"></i></a>
<div style="margin-top: 0.3em">
<tiny-basic-label v-if="origin.isOSS"><i class="icon hdd outline"></i>对象存储</tiny-basic-label>
<tiny-basic-label v-if="origin.name.length > 0">{{origin.name}}</tiny-basic-label>
<tiny-basic-label v-if="origin.hasCert">证书</tiny-basic-label>
<tiny-basic-label v-if="origin.host != null && origin.host.length > 0">主机名: {{origin.host}}</tiny-basic-label>
<tiny-basic-label v-if="origin.followPort">端口跟随</tiny-basic-label>
<tiny-basic-label v-if="origin.addr != null && origin.addr.startsWith('https://') && origin.http2Enabled">HTTP/2</tiny-basic-label>
<tiny-basic-label class="grey border-grey" v-if="origin.isOSS"><i class="icon hdd outline"></i>对象存储</tiny-basic-label>
<tiny-basic-label class="grey border-grey" v-if="origin.name.length > 0">{{origin.name}}</tiny-basic-label>
<tiny-basic-label class="grey border-grey" v-if="origin.hasCert">证书</tiny-basic-label>
<tiny-basic-label class="grey border-grey" v-if="origin.host != null && origin.host.length > 0">主机名: {{origin.host}}</tiny-basic-label>
<tiny-basic-label class="grey border-grey" v-if="origin.followPort">端口跟随</tiny-basic-label>
<tiny-basic-label class="grey border-grey" v-if="origin.addr != null && origin.addr.startsWith('https://') && origin.http2Enabled">HTTP/2</tiny-basic-label>
<span v-if="origin.domains != null && origin.domains.length > 0"><tiny-basic-label v-for="domain in origin.domains">匹配: {{domain}}</tiny-basic-label></span>
<span v-else-if="hasMatchedDomains"><tiny-basic-label>匹配: 所有域名</tiny-basic-label></span>
<span v-if="origin.domains != null && origin.domains.length > 0"><tiny-basic-label class="grey border-grey" v-for="domain in origin.domains">匹配: {{domain}}</tiny-basic-label></span>
<span v-else-if="hasMatchedDomains"><tiny-basic-label class="grey border-grey">匹配: 所有域名</tiny-basic-label></span>
</div>
</td>
<td :class="{disabled:!origin.isOn}">{{origin.weight}}</td>
@@ -7455,7 +7479,8 @@ Vue.component("origin-list-table", {
</td>
<td>
<a href="" @click.prevent="updateOrigin(origin.id)">修改</a> &nbsp;
<a href="" @click.prevent="deleteOrigin(origin.id)">删除</a>
<a href="" v-if="origin.isOn" @click.prevent="updateOriginIsOn(origin.id, origin.addr, false)">停用</a><a href="" v-if="!origin.isOn" @click.prevent="updateOriginIsOn(origin.id, origin.addr, true)"><span class="red">启用</span></a> &nbsp;
<a href="" @click.prevent="deleteOrigin(origin.id, origin.addr)">删除</a>
</td>
</tr>
</tbody>
@@ -8251,9 +8276,8 @@ Vue.component("uam-config-box", {
<td>匹配条件</td>
<td>
<http-request-conds-box :v-conds="config.conds" @change="changeConds"></http-request-conds-box>
</td>
</td>
</tr>
</tr>
</tbody>
</table>
<div class="margin"></div>
@@ -8673,7 +8697,8 @@ Vue.component("http-referers-config-box", {
config.denyDomains = []
}
return {
config: config
config: config,
moreOptionsVisible: false
}
},
methods: {
@@ -8691,6 +8716,9 @@ Vue.component("http-referers-config-box", {
this.config.denyDomains = domains
this.$forceUpdate()
}
},
showMoreOptions: function () {
this.moreOptionsVisible = !this.moreOptionsVisible
}
},
template: `<div>
@@ -8738,6 +8766,11 @@ Vue.component("http-referers-config-box", {
<p class="comment">禁止的来源域名列表,比如<code-label>example.org</code-label>、<code-label>*.example.org</code-label>;除了这些禁止的来源域名外,其他域名都会被允许,除非限定了允许的来源域名。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator @change="showMoreOptions"></more-options-indicator></td>
</tr>
</tbody>
<tbody v-show="moreOptionsVisible && isOn()">
<tr>
<td>同时检查Origin</td>
<td>
@@ -8745,6 +8778,20 @@ Vue.component("http-referers-config-box", {
<p class="comment">如果请求没有指定Referer Header则尝试检查Origin Header多用于跨站调用。</p>
</td>
</tr>
<tr>
<td>例外URL</td>
<td>
<url-patterns-box v-model="config.exceptURLPatterns"></url-patterns-box>
<p class="comment">如果填写了例外URL表示这些URL跳过不做处理。</p>
</td>
</tr>
<tr>
<td>限制URL</td>
<td>
<url-patterns-box v-model="config.onlyURLPatterns"></url-patterns-box>
<p class="comment">如果填写了限制URL表示只对这些URL进行处理如果不填则表示支持所有的URL。</p>
</td>
</tr>
</tbody>
</table>
<div class="ui margin"></div>
@@ -10872,6 +10919,137 @@ Vue.component("http-firewall-page-options", {
`
})
Vue.component("http-firewall-js-cookie-options", {
props: ["v-js-cookie-options"],
mounted: function () {
this.updateSummary()
},
data: function () {
let options = this.vJsCookieOptions
if (options == null) {
options = {
life: 0,
maxFails: 0,
failBlockTimeout: 0,
failBlockScopeAll: false,
scope: "service"
}
}
return {
options: options,
isEditing: false,
summary: ""
}
},
watch: {
"options.life": function (v) {
let i = parseInt(v, 10)
if (isNaN(i)) {
i = 0
}
this.options.life = i
this.updateSummary()
},
"options.maxFails": function (v) {
let i = parseInt(v, 10)
if (isNaN(i)) {
i = 0
}
this.options.maxFails = i
this.updateSummary()
},
"options.failBlockTimeout": function (v) {
let i = parseInt(v, 10)
if (isNaN(i)) {
i = 0
}
this.options.failBlockTimeout = i
this.updateSummary()
},
"options.failBlockScopeAll": function (v) {
this.updateSummary()
}
},
methods: {
edit: function () {
this.isEditing = !this.isEditing
},
updateSummary: function () {
let summaryList = []
if (this.options.life > 0) {
summaryList.push("有效时间" + this.options.life + "秒")
}
if (this.options.maxFails > 0) {
summaryList.push("最多失败" + this.options.maxFails + "次")
}
if (this.options.failBlockTimeout > 0) {
summaryList.push("失败拦截" + this.options.failBlockTimeout + "秒")
}
if (this.options.failBlockScopeAll) {
summaryList.push("尝试全局封禁")
}
if (summaryList.length == 0) {
this.summary = "默认配置"
} else {
this.summary = summaryList.join(" / ")
}
},
confirm: function () {
this.isEditing = false
}
},
template: `<div>
<input type="hidden" name="jsCookieOptionsJSON" :value="JSON.stringify(options)"/>
<a href="" @click.prevent="edit">{{summary}} <i class="icon angle" :class="{up: isEditing, down: !isEditing}"></i></a>
<div v-show="isEditing" style="margin-top: 0.5em">
<table class="ui table definition selectable">
<tbody>
<tr>
<td class="title">有效时间</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="9" v-model="options.life" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span>
</div>
<p class="comment">验证通过后在这个时间内不再验证默认3600秒。</p>
</td>
</tr>
<tr>
<td>最多失败次数</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="9" v-model="options.maxFails" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">次</span>
</div>
<p class="comment"><span v-if="options.maxFails > 0 && options.maxFails < 5" class="red">建议填入一个不小于5的数字以减少误判几率。</span>允许用户失败尝试的最多次数超过这个次数将被自动加入黑名单。如果为空或者为0表示不限制。</p>
</td>
</tr>
<tr>
<td>失败拦截时间</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="9" v-model="options.failBlockTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span>
</div>
<p class="comment">在达到最多失败次数大于0自动拦截的时长如果为0表示不自动拦截。</p>
</td>
</tr>
<tr>
<td>失败全局封禁</td>
<td>
<checkbox v-model="options.failBlockScopeAll"></checkbox>
<p class="comment">选中后表示允许系统尝试全局封禁某个IP以提升封禁性能。</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
`
})
// 压缩配置
Vue.component("http-compression-config-box", {
props: ["v-compression-config", "v-is-location", "v-is-group"],
@@ -10889,7 +11067,7 @@ Vue.component("http-compression-config-box", {
isOn: false,
useDefaultTypes: true,
types: ["brotli", "gzip", "zstd", "deflate"],
level: 5,
level: 0,
decompressData: false,
gzipRef: null,
deflateRef: null,
@@ -10900,7 +11078,9 @@ Vue.component("http-compression-config-box", {
extensions: [".js", ".json", ".html", ".htm", ".xml", ".css", ".woff2", ".txt"],
exceptExtensions: [".apk", ".ipa"],
conds: null,
enablePartialContent: false
enablePartialContent: false,
onlyURLPatterns: [],
exceptURLPatterns: []
}
}
@@ -10959,19 +11139,6 @@ Vue.component("http-compression-config-box", {
allTypes: configTypes
}
},
watch: {
"config.level": function (v) {
let level = parseInt(v)
if (isNaN(level)) {
level = 1
} else if (level < 1) {
level = 1
} else if (level > 10) {
level = 10
}
this.config.level = level
}
},
methods: {
isOn: function () {
return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn
@@ -11047,15 +11214,6 @@ Vue.component("http-compression-config-box", {
</tr>
</tbody>
<tbody v-show="isOn()">
<tr>
<td>压缩级别</td>
<td>
<select class="ui dropdown auto-width" v-model="config.level">
<option v-for="i in 10" :value="i">{{i}}</option>
</select>
<p class="comment">级别越高,压缩比例越大。</p>
</td>
</tr>
<tr>
<td>支持的扩展名</td>
<td>
@@ -11128,12 +11286,26 @@ Vue.component("http-compression-config-box", {
<checkbox v-model="config.enablePartialContent"></checkbox>
<p class="comment">支持对分片内容PartialContent的压缩除非客户端有特殊要求一般不需要启用。</p>
</td>
</tr>
<tr>
<td>例外URL</td>
<td>
<url-patterns-box v-model="config.exceptURLPatterns"></url-patterns-box>
<p class="comment">如果填写了例外URL表示这些URL跳过不做处理。</p>
</td>
</tr>
<tr>
<td>限制URL</td>
<td>
<url-patterns-box v-model="config.onlyURLPatterns"></url-patterns-box>
<p class="comment">如果填写了限制URL表示只对这些URL进行压缩处理如果不填则表示支持所有的URL。</p>
</td>
</tr>
<tr>
<td>匹配条件</td>
<td>
<http-request-conds-box :v-conds="config.conds" @change="changeConds"></http-request-conds-box>
</td>
</td>
</tr>
</tbody>
</table>
@@ -11154,7 +11326,8 @@ Vue.component("http-cc-config-box", {
enableGET302: true,
onlyURLPatterns: [],
exceptURLPatterns: [],
useDefaultThresholds: true
useDefaultThresholds: true,
ignoreCommonFiles: true
}
}
@@ -11276,7 +11449,14 @@ Vue.component("http-cc-config-box", {
<url-patterns-box v-model="config.onlyURLPatterns"></url-patterns-box>
<p class="comment">如果填写了限制URL表示只对这些URL进行CC防护处理如果不填则表示支持所有的URL。</p>
</td>
</tr>
</tr>
<tr>
<td>忽略常用文件</td>
<td>
<checkbox v-model="config.ignoreCommonFiles"></checkbox>
<p class="comment">忽略js、css、jpg等常在网页里被引用的文件名即对这些文件的访问不加入计数可以减少误判几率。</p>
</td>
</tr>
<tr>
<td>检查请求来源指纹</td>
<td>
@@ -12020,6 +12200,7 @@ Vue.component("http-firewall-block-options-viewer", {
<span v-if="options == null">默认设置</span>
<div v-else>
状态码:{{options.statusCode}} / 提示内容:<span v-if="options.body != null && options.body.length > 0">[{{options.body.length}}字符]</span><span v-else class="disabled">[无]</span> / 超时时间:{{options.timeout}}秒 <span v-if="options.timeoutMax > options.timeout">/ 最大封禁时长:{{options.timeoutMax}}秒</span>
<span v-if="options.failBlockScopeAll"> / 尝试全局封禁</span>
</div>
</div>
`
@@ -12550,6 +12731,54 @@ Vue.component("script-config-box", {
</div>`
})
Vue.component("http-firewall-js-cookie-options-viewer", {
props: ["v-js-cookie-options"],
mounted: function () {
this.updateSummary()
},
data: function () {
let options = this.vJsCookieOptions
if (options == null) {
options = {
life: 0,
maxFails: 0,
failBlockTimeout: 0,
failBlockScopeAll: false,
scope: ""
}
}
return {
options: options,
summary: ""
}
},
methods: {
updateSummary: function () {
let summaryList = []
if (this.options.life > 0) {
summaryList.push("有效时间" + this.options.life + "秒")
}
if (this.options.maxFails > 0) {
summaryList.push("最多失败" + this.options.maxFails + "次")
}
if (this.options.failBlockTimeout > 0) {
summaryList.push("失败拦截" + this.options.failBlockTimeout + "秒")
}
if (this.options.failBlockScopeAll) {
summaryList.push("尝试全局封禁")
}
if (summaryList.length == 0) {
this.summary = "默认配置"
} else {
this.summary = summaryList.join(" / ")
}
}
},
template: `<div>{{summary}}</div>
`
})
Vue.component("ssl-certs-view", {
props: ["v-certs"],
data: function () {
@@ -13861,7 +14090,7 @@ Vue.component("http-firewall-block-options", {
props: ["v-block-options"],
data: function () {
return {
blockOptions: this.vBlockOptions,
options: this.vBlockOptions,
statusCode: this.vBlockOptions.statusCode,
timeout: this.vBlockOptions.timeout,
timeoutMax: this.vBlockOptions.timeoutMax,
@@ -13872,25 +14101,25 @@ Vue.component("http-firewall-block-options", {
statusCode: function (v) {
let statusCode = parseInt(v)
if (isNaN(statusCode)) {
this.blockOptions.statusCode = 403
this.options.statusCode = 403
} else {
this.blockOptions.statusCode = statusCode
this.options.statusCode = statusCode
}
},
timeout: function (v) {
let timeout = parseInt(v)
if (isNaN(timeout)) {
this.blockOptions.timeout = 0
this.options.timeout = 0
} else {
this.blockOptions.timeout = timeout
this.options.timeout = timeout
}
},
timeoutMax: function (v) {
let timeoutMax = parseInt(v)
if (isNaN(timeoutMax)) {
this.blockOptions.timeoutMax = 0
this.options.timeoutMax = 0
} else {
this.blockOptions.timeoutMax = timeoutMax
this.options.timeoutMax = timeoutMax
}
}
},
@@ -13900,9 +14129,10 @@ Vue.component("http-firewall-block-options", {
}
},
template: `<div>
<input type="hidden" name="blockOptionsJSON" :value="JSON.stringify(blockOptions)"/>
<a href="" @click.prevent="edit">状态码:{{statusCode}} / 提示内容:<span v-if="blockOptions.body != null && blockOptions.body.length > 0">[{{blockOptions.body.length}}字符]</span><span v-else class="disabled">[无]</span> <span v-if="timeout > 0"> / 封禁时长:{{timeout}}秒</span>
<input type="hidden" name="blockOptionsJSON" :value="JSON.stringify(options)"/>
<a href="" @click.prevent="edit">状态码:{{statusCode}} / 提示内容:<span v-if="options.body != null && options.body.length > 0">[{{options.body.length}}字符]</span><span v-else class="disabled">[无]</span> <span v-if="timeout > 0"> / 封禁时长:{{timeout}}秒</span>
<span v-if="timeoutMax > timeout"> / 最大封禁时长:{{timeoutMax}}秒</span>
<span v-if="options.failBlockScopeAll"> / 尝试全局封禁</span>
<i class="icon angle" :class="{up: isEditing, down: !isEditing}"></i></a>
<table class="ui table" v-show="isEditing">
<tr>
@@ -13914,7 +14144,7 @@ Vue.component("http-firewall-block-options", {
<tr>
<td>提示内容</td>
<td>
<textarea rows="3" v-model="blockOptions.body"></textarea>
<textarea rows="3" v-model="options.body"></textarea>
</td>
</tr>
<tr>
@@ -13937,6 +14167,13 @@ Vue.component("http-firewall-block-options", {
<p class="comment">如果最大封禁时长大于封禁时长({{timeout}}秒),那么表示每次封禁的时候,将会在这两个时长数字之间随机选取一个数字作为最终的封禁时长。</p>
</td>
</tr>
<tr>
<td>失败全局封禁</td>
<td>
<checkbox v-model="options.failBlockScopeAll"></checkbox>
<p class="comment">选中后表示允许系统尝试全局封禁某个IP以提升封禁性能。</p>
</td>
</tr>
</table>
</div>
`
@@ -15883,7 +16120,7 @@ Vue.component("http-firewall-captcha-options", {
summaryList.push("失败拦截" + this.options.failBlockTimeout + "秒")
}
if (this.options.failBlockScopeAll) {
summaryList.push("全局封禁")
summaryList.push("尝试全局封禁")
}
let that = this
@@ -15963,7 +16200,7 @@ Vue.component("http-firewall-captcha-options", {
<td>失败全局封禁</td>
<td>
<checkbox v-model="options.failBlockScopeAll"></checkbox>
<p class="comment">是否在失败时全局封禁,默认为只封禁对单个网站的访问。</p>
<p class="comment">选中后表示允许系统尝试全局封禁某个IP以提升封禁性能。</p>
</td>
</tr>
@@ -16080,7 +16317,9 @@ Vue.component("user-agent-config-box", {
addingFilter: {
keywords: [],
action: "deny"
}
},
moreOptionsVisible: false,
batchKeywords: ""
}
},
methods: {
@@ -16095,6 +16334,10 @@ Vue.component("user-agent-config-box", {
},
add: function () {
this.isAdding = true
let that = this
setTimeout(function () {
that.$refs.batchKeywords.focus()
})
},
confirm: function () {
if (this.addingFilter.action == "deny") {
@@ -16122,9 +16365,21 @@ Vue.component("user-agent-config-box", {
keywords: [],
action: "deny"
}
this.batchKeywords = ""
},
changeKeywords: function (keywords) {
this.addingFilter.keywords = keywords
let arr = keywords.split(/\n/)
let resultKeywords = []
arr.forEach(function (keyword){
keyword = keyword.trim()
if (!resultKeywords.$contains(keyword)) {
resultKeywords.push(keyword)
}
})
this.addingFilter.keywords = resultKeywords
},
showMoreOptions: function () {
this.moreOptionsVisible = !this.moreOptionsVisible
}
},
template: `<div>
@@ -16177,8 +16432,8 @@ Vue.component("user-agent-config-box", {
<tr>
<td class="title">UA关键词</td>
<td>
<values-box :v-values="addingFilter.keywords" :v-allow-empty="true" @change="changeKeywords"></values-box>
<p class="comment">不区分大小写,比如<code-label>Chrome</code-label>;支持<code-label>*</code-label>通配符,比如<code-label>*Firefox*</code-label>;也支持空关键词表示空UserAgent。</p>
<textarea v-model="batchKeywords" @input="changeKeywords(batchKeywords)" ref="batchKeywords" style="width: 20em" placeholder="*浏览器标识*"></textarea>
<p class="comment">每行一个关键词;不区分大小写,比如<code-label>Chrome</code-label>;支持<code-label>*</code-label>通配符,比如<code-label>*Firefox*</code-label>;也支持空关键词表示空UserAgent。</p>
</td>
</tr>
<tr>
@@ -16197,6 +16452,25 @@ Vue.component("user-agent-config-box", {
</div>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator @change="showMoreOptions"></more-options-indicator></td>
</tr>
</tbody>
<tbody v-show="moreOptionsVisible && isOn()">
<tr>
<td>例外URL</td>
<td>
<url-patterns-box v-model="config.exceptURLPatterns"></url-patterns-box>
<p class="comment">如果填写了例外URL表示这些URL跳过不做处理。</p>
</td>
</tr>
<tr>
<td>限制URL</td>
<td>
<url-patterns-box v-model="config.onlyURLPatterns"></url-patterns-box>
<p class="comment">如果填写了限制URL表示只对这些URL进行处理如果不填则表示支持所有的URL。</p>
</td>
</tr>
</tbody>
</table>
<div class="margin"></div>
@@ -16703,7 +16977,7 @@ Vue.component("ip-list-table", {
<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>
<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>
@@ -16735,8 +17009,12 @@ Vue.component("ip-list-table", {
</td>
<td>
<span v-if="item.type != 'all'" :class="{green: item.list != null && item.list.type == 'white'}">
<keyword :v-word="keyword">{{item.ipFrom}}</keyword> <span> <span class="small red" v-if="item.isRead != null && !item.isRead">&nbsp;New&nbsp;</span>&nbsp;<a :href="'/servers/iplists?ip=' + item.ipFrom" v-if="vShowSearchButton" title="搜索此IP"><span><i class="icon search small" style="color: #ccc"></i></span></a></span>
<span v-if="item.ipTo.length > 0"> - <keyword :v-word="keyword">{{item.ipTo}}</keyword></span></span>
<span v-if="item.value != null && item.value.length > 0"><keyword :v-word="keyword">{{item.value}}</keyword></span>
<span v-else>
<keyword :v-word="keyword">{{item.ipFrom}}</keyword> <span> <span class="small red" v-if="item.isRead != null && !item.isRead">&nbsp;New&nbsp;</span>&nbsp;<a :href="'/servers/iplists?ip=' + item.ipFrom" v-if="vShowSearchButton" title="搜索此IP"><span><i class="icon search small" style="color: #ccc"></i></span></a></span>
<span v-if="item.ipTo.length > 0"> - <keyword :v-word="keyword">{{item.ipTo}}</keyword></span>
</span>
</span>
<span v-else class="disabled">*</span>
<div v-if="item.region != null && item.region.length > 0">
@@ -16755,8 +17033,8 @@ Vue.component("ip-list-table", {
<span v-if="item.policy.id > 0">
<span v-if="item.policy.server != null">
<a :href="'/servers/server/settings/waf/ipadmin/allowList?serverId=' + item.policy.server.id + '&firewallPolicyId=' + item.policy.id" v-if="item.list.type == 'white'">[服务{{item.policy.server.name}}]</a>
<a :href="'/servers/server/settings/waf/ipadmin/denyList?serverId=' + item.policy.server.id + '&firewallPolicyId=' + item.policy.id" v-if="item.list.type == 'black'">[服务{{item.policy.server.name}}]</a>
<a :href="'/servers/server/settings/waf/ipadmin/allowList?serverId=' + item.policy.server.id + '&firewallPolicyId=' + item.policy.id" v-if="item.list.type == 'white'">[网站{{item.policy.server.name}}]</a>
<a :href="'/servers/server/settings/waf/ipadmin/denyList?serverId=' + item.policy.server.id + '&firewallPolicyId=' + item.policy.id" v-if="item.list.type == 'black'">[网站{{item.policy.server.name}}]</a>
</span>
<span v-else>
<a :href="'/servers/components/waf/ipadmin/lists?firewallPolicyId=' + item.policy.id + '&type=' + item.list.type">[策略:{{item.policy.name}}]</a>
@@ -16779,11 +17057,12 @@ Vue.component("ip-list-table", {
<td>
<div v-if="item.expiredTime.length > 0">
{{item.expiredTime}}
<div v-if="item.isExpired" style="margin-top: 0.5em">
<div v-if="item.isExpired && item.lifeSeconds == null" style="margin-top: 0.5em">
<span class="ui label tiny basic red">已过期</span>
</div>
<div v-if="item.lifeSeconds != null && item.lifeSeconds > 0">
<span class="small grey">{{formatSeconds(item.lifeSeconds)}}</span>
<div v-if="item.lifeSeconds != null">
<span class="small grey" v-if="item.lifeSeconds > 0">{{formatSeconds(item.lifeSeconds)}}</span>
<span class="small red" v-if="item.lifeSeconds < 0">已过期</span>
</div>
</div>
<span v-else class="disabled">不过期</span>
@@ -16818,11 +17097,13 @@ Vue.component("ip-item-text", {
props: ["v-item"],
template: `<span>
<span v-if="vItem.type == 'all'">*</span>
<span v-if="vItem.type == 'ipv4' || vItem.type.length == 0">
{{vItem.ipFrom}}
<span v-if="vItem.ipTo.length > 0">- {{vItem.ipTo}}</span>
</span>
<span v-if="vItem.type == 'ipv6'">{{vItem.ipFrom}}</span>
<span v-else>
<span v-if="vItem.value != null && vItem.value.length > 0">{{vItem.value}}</span>
<span v-else>
{{vItem.ipFrom}}
<span v-if="vItem.ipTo != null &&vItem.ipTo.length > 0">- {{vItem.ipTo}}</span>
</span>
</span>
<span v-if="vItem.eventLevelName != null && vItem.eventLevelName.length > 0">&nbsp; 级别:{{vItem.eventLevelName}}</span>
</span>`
})
@@ -17945,6 +18226,15 @@ Vue.component("datetime-input", {
resultTimestamp: function () {
return this.timestamp
},
nextYear: function () {
let date = new Date()
date.setFullYear(date.getFullYear()+1)
this.day = date.getFullYear() + "-" + this.leadingZero(date.getMonth() + 1, 2) + "-" + this.leadingZero(date.getDate(), 2)
this.hour = this.leadingZero(date.getHours(), 2)
this.minute = this.leadingZero(date.getMinutes(), 2)
this.second = this.leadingZero(date.getSeconds(), 2)
this.change()
},
nextDays: function (days) {
let date = new Date()
date.setTime(date.getTime() + days * 86400 * 1000)
@@ -17976,7 +18266,7 @@ Vue.component("datetime-input", {
<div class="ui field">:</div>
<div class="ui field" :class="{error: hasSecondError}"><input type="text" v-model="second" maxlength="2" style="width:4em" placeholder="秒" @input="change"/></div>
</div>
<p class="comment">常用时间:<a href="" @click.prevent="nextHours(1)"> &nbsp;1小时&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(1)"> &nbsp;1天&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(3)"> &nbsp;3天&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(7)"> &nbsp;1周&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(30)"> &nbsp;30天&nbsp; </a> </p>
<p class="comment">常用时间:<a href="" @click.prevent="nextHours(1)"> &nbsp;1小时&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(1)"> &nbsp;1天&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(3)"> &nbsp;3天&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(7)"> &nbsp;1周&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(30)"> &nbsp;30天&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextYear()"> &nbsp;1年&nbsp; </a> </p>
</div>`
})
@@ -19291,13 +19581,15 @@ Vue.component("url-patterns-box", {
}
},
confirm: function () {
let pattern = this.addingPattern.pattern.trim()
if (pattern.length == 0) {
let that = this
teaweb.warn("请输入URL", function () {
that.$refs.patternInput.focus()
})
return
if (this.requireURL(this.addingPattern.type)) {
let pattern = this.addingPattern.pattern.trim()
if (pattern.length == 0) {
let that = this
teaweb.warn("请输入URL", function () {
that.$refs.patternInput.focus()
})
return
}
}
if (this.editingIndex < 0) {
this.patterns.push({
@@ -19327,6 +19619,12 @@ Vue.component("url-patterns-box", {
return "通配符"
case "regexp":
return "正则"
case "images":
return "常见图片文件"
case "audios":
return "常见音频文件"
case "videos":
return "常见视频文件"
}
return ""
},
@@ -19353,6 +19651,9 @@ Vue.component("url-patterns-box", {
}
break
}
},
requireURL: function (patternType) {
return patternType == "wildcard" || patternType == "regexp"
}
},
template: `<div>
@@ -19369,14 +19670,17 @@ Vue.component("url-patterns-box", {
<select class="ui dropdown auto-width" v-model="addingPattern.type">
<option value="wildcard">通配符</option>
<option value="regexp">正则表达式</option>
<option value="images">常见图片</option>
<option value="audios">常见音频</option>
<option value="videos">常见视频</option>
</select>
</div>
<div class="ui field">
<div class="ui field" v-show="addingPattern.type == 'wildcard' || addingPattern.type == 'regexp'">
<input type="text" :placeholder="(addingPattern.type == 'wildcard') ? '可以使用星号(*)通配符,不区分大小写' : '可以使用正则表达式,不区分大小写'" v-model="addingPattern.pattern" @input="changePattern" size="36" ref="patternInput" @keyup.enter="confirm()" @keypress.enter.prevent="1" spellcheck="false"/>
<p class="comment" v-if="patternIsInvalid"><span class="red" style="font-weight: normal"><span v-if="addingPattern.type == 'wildcard'">通配符</span><span v-if="addingPattern.type == 'regexp'">正则表达式</span>中不能包含问号(?)及问号以后的内容。</span></p>
</div>
<div class="ui field" style="padding-left: 0">
<tip-icon content="通配符示例:<br/>单个路径开头:/hello/world/*<br/>单个路径结尾:*/hello/world<br/>包含某个路径:*/article/*<br/>某个域名下的所有URL*example.com/*" v-if="addingPattern.type == 'wildcard'"></tip-icon>
<div class="ui field" style="padding-left: 0" v-show="addingPattern.type == 'wildcard' || addingPattern.type == 'regexp'">
<tip-icon content="通配符示例:<br/>单个路径开头:/hello/world/*<br/>单个路径结尾:*/hello/world<br/>包含某个路径:*/article/*<br/>某个域名下的所有URL*example.com/*<br/>忽略某个扩展名:*.js" v-if="addingPattern.type == 'wildcard'"></tip-icon>
<tip-icon content="正则表达式示例:<br/>单个路径开头:^/hello/world<br/>单个路径结尾:/hello/world$<br/>包含某个路径:/article/<br/>匹配某个数字路径:/article/(\\d+)<br/>某个域名下的所有URL^(http|https)://example.com/" v-if="addingPattern.type == 'regexp'"></tip-icon>
</div>
<div class="ui field">

View File

@@ -140,6 +140,15 @@ Vue.component("datetime-input", {
resultTimestamp: function () {
return this.timestamp
},
nextYear: function () {
let date = new Date()
date.setFullYear(date.getFullYear()+1)
this.day = date.getFullYear() + "-" + this.leadingZero(date.getMonth() + 1, 2) + "-" + this.leadingZero(date.getDate(), 2)
this.hour = this.leadingZero(date.getHours(), 2)
this.minute = this.leadingZero(date.getMinutes(), 2)
this.second = this.leadingZero(date.getSeconds(), 2)
this.change()
},
nextDays: function (days) {
let date = new Date()
date.setTime(date.getTime() + days * 86400 * 1000)
@@ -171,6 +180,6 @@ Vue.component("datetime-input", {
<div class="ui field">:</div>
<div class="ui field" :class="{error: hasSecondError}"><input type="text" v-model="second" maxlength="2" style="width:4em" placeholder="秒" @input="change"/></div>
</div>
<p class="comment">常用时间:<a href="" @click.prevent="nextHours(1)"> &nbsp;1小时&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(1)"> &nbsp;1天&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(3)"> &nbsp;3天&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(7)"> &nbsp;1周&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(30)"> &nbsp;30天&nbsp; </a> </p>
<p class="comment">常用时间:<a href="" @click.prevent="nextHours(1)"> &nbsp;1小时&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(1)"> &nbsp;1天&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(3)"> &nbsp;3天&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(7)"> &nbsp;1周&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextDays(30)"> &nbsp;30天&nbsp; </a> <span class="disabled">|</span> <a href="" @click.prevent="nextYear()"> &nbsp;1年&nbsp; </a> </p>
</div>`
})

View File

@@ -35,13 +35,15 @@ Vue.component("url-patterns-box", {
}
},
confirm: function () {
let pattern = this.addingPattern.pattern.trim()
if (pattern.length == 0) {
let that = this
teaweb.warn("请输入URL", function () {
that.$refs.patternInput.focus()
})
return
if (this.requireURL(this.addingPattern.type)) {
let pattern = this.addingPattern.pattern.trim()
if (pattern.length == 0) {
let that = this
teaweb.warn("请输入URL", function () {
that.$refs.patternInput.focus()
})
return
}
}
if (this.editingIndex < 0) {
this.patterns.push({
@@ -71,6 +73,12 @@ Vue.component("url-patterns-box", {
return "通配符"
case "regexp":
return "正则"
case "images":
return "常见图片文件"
case "audios":
return "常见音频文件"
case "videos":
return "常见视频文件"
}
return ""
},
@@ -97,6 +105,9 @@ Vue.component("url-patterns-box", {
}
break
}
},
requireURL: function (patternType) {
return patternType == "wildcard" || patternType == "regexp"
}
},
template: `<div>
@@ -113,14 +124,17 @@ Vue.component("url-patterns-box", {
<select class="ui dropdown auto-width" v-model="addingPattern.type">
<option value="wildcard">通配符</option>
<option value="regexp">正则表达式</option>
<option value="images">常见图片</option>
<option value="audios">常见音频</option>
<option value="videos">常见视频</option>
</select>
</div>
<div class="ui field">
<div class="ui field" v-show="addingPattern.type == 'wildcard' || addingPattern.type == 'regexp'">
<input type="text" :placeholder="(addingPattern.type == 'wildcard') ? '可以使用星号(*)通配符,不区分大小写' : '可以使用正则表达式,不区分大小写'" v-model="addingPattern.pattern" @input="changePattern" size="36" ref="patternInput" @keyup.enter="confirm()" @keypress.enter.prevent="1" spellcheck="false"/>
<p class="comment" v-if="patternIsInvalid"><span class="red" style="font-weight: normal"><span v-if="addingPattern.type == 'wildcard'">通配符</span><span v-if="addingPattern.type == 'regexp'">正则表达式</span>中不能包含问号(?)及问号以后的内容。</span></p>
</div>
<div class="ui field" style="padding-left: 0">
<tip-icon content="通配符示例:<br/>单个路径开头:/hello/world/*<br/>单个路径结尾:*/hello/world<br/>包含某个路径:*/article/*<br/>某个域名下的所有URL*example.com/*" v-if="addingPattern.type == 'wildcard'"></tip-icon>
<div class="ui field" style="padding-left: 0" v-show="addingPattern.type == 'wildcard' || addingPattern.type == 'regexp'">
<tip-icon content="通配符示例:<br/>单个路径开头:/hello/world/*<br/>单个路径结尾:*/hello/world<br/>包含某个路径:*/article/*<br/>某个域名下的所有URL*example.com/*<br/>忽略某个扩展名:*.js" v-if="addingPattern.type == 'wildcard'"></tip-icon>
<tip-icon content="正则表达式示例:<br/>单个路径开头:^/hello/world<br/>单个路径结尾:/hello/world$<br/>包含某个路径:/article/<br/>匹配某个数字路径:/article/(\\d+)<br/>某个域名下的所有URL^(http|https)://example.com/" v-if="addingPattern.type == 'regexp'"></tip-icon>
</div>
<div class="ui field">

View File

@@ -2,11 +2,13 @@ Vue.component("ip-item-text", {
props: ["v-item"],
template: `<span>
<span v-if="vItem.type == 'all'">*</span>
<span v-if="vItem.type == 'ipv4' || vItem.type.length == 0">
{{vItem.ipFrom}}
<span v-if="vItem.ipTo.length > 0">- {{vItem.ipTo}}</span>
</span>
<span v-if="vItem.type == 'ipv6'">{{vItem.ipFrom}}</span>
<span v-else>
<span v-if="vItem.value != null && vItem.value.length > 0">{{vItem.value}}</span>
<span v-else>
{{vItem.ipFrom}}
<span v-if="vItem.ipTo != null &&vItem.ipTo.length > 0">- {{vItem.ipTo}}</span>
</span>
</span>
<span v-if="vItem.eventLevelName != null && vItem.eventLevelName.length > 0">&nbsp; 级别:{{vItem.eventLevelName}}</span>
</span>`
})

View File

@@ -123,7 +123,7 @@ Vue.component("ip-list-table", {
<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>
<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>
@@ -155,8 +155,12 @@ Vue.component("ip-list-table", {
</td>
<td>
<span v-if="item.type != 'all'" :class="{green: item.list != null && item.list.type == 'white'}">
<keyword :v-word="keyword">{{item.ipFrom}}</keyword> <span> <span class="small red" v-if="item.isRead != null && !item.isRead">&nbsp;New&nbsp;</span>&nbsp;<a :href="'/servers/iplists?ip=' + item.ipFrom" v-if="vShowSearchButton" title="搜索此IP"><span><i class="icon search small" style="color: #ccc"></i></span></a></span>
<span v-if="item.ipTo.length > 0"> - <keyword :v-word="keyword">{{item.ipTo}}</keyword></span></span>
<span v-if="item.value != null && item.value.length > 0"><keyword :v-word="keyword">{{item.value}}</keyword></span>
<span v-else>
<keyword :v-word="keyword">{{item.ipFrom}}</keyword> <span> <span class="small red" v-if="item.isRead != null && !item.isRead">&nbsp;New&nbsp;</span>&nbsp;<a :href="'/servers/iplists?ip=' + item.ipFrom" v-if="vShowSearchButton" title="搜索此IP"><span><i class="icon search small" style="color: #ccc"></i></span></a></span>
<span v-if="item.ipTo.length > 0"> - <keyword :v-word="keyword">{{item.ipTo}}</keyword></span>
</span>
</span>
<span v-else class="disabled">*</span>
<div v-if="item.region != null && item.region.length > 0">
@@ -175,8 +179,8 @@ Vue.component("ip-list-table", {
<span v-if="item.policy.id > 0">
<span v-if="item.policy.server != null">
<a :href="'/servers/server/settings/waf/ipadmin/allowList?serverId=' + item.policy.server.id + '&firewallPolicyId=' + item.policy.id" v-if="item.list.type == 'white'">[服务{{item.policy.server.name}}]</a>
<a :href="'/servers/server/settings/waf/ipadmin/denyList?serverId=' + item.policy.server.id + '&firewallPolicyId=' + item.policy.id" v-if="item.list.type == 'black'">[服务{{item.policy.server.name}}]</a>
<a :href="'/servers/server/settings/waf/ipadmin/allowList?serverId=' + item.policy.server.id + '&firewallPolicyId=' + item.policy.id" v-if="item.list.type == 'white'">[网站{{item.policy.server.name}}]</a>
<a :href="'/servers/server/settings/waf/ipadmin/denyList?serverId=' + item.policy.server.id + '&firewallPolicyId=' + item.policy.id" v-if="item.list.type == 'black'">[网站{{item.policy.server.name}}]</a>
</span>
<span v-else>
<a :href="'/servers/components/waf/ipadmin/lists?firewallPolicyId=' + item.policy.id + '&type=' + item.list.type">[策略:{{item.policy.name}}]</a>
@@ -199,11 +203,12 @@ Vue.component("ip-list-table", {
<td>
<div v-if="item.expiredTime.length > 0">
{{item.expiredTime}}
<div v-if="item.isExpired" style="margin-top: 0.5em">
<div v-if="item.isExpired && item.lifeSeconds == null" style="margin-top: 0.5em">
<span class="ui label tiny basic red">已过期</span>
</div>
<div v-if="item.lifeSeconds != null && item.lifeSeconds > 0">
<span class="small grey">{{formatSeconds(item.lifeSeconds)}}</span>
<div v-if="item.lifeSeconds != null">
<span class="small grey" v-if="item.lifeSeconds > 0">{{formatSeconds(item.lifeSeconds)}}</span>
<span class="small red" v-if="item.lifeSeconds < 0">已过期</span>
</div>
</div>
<span v-else class="disabled">不过期</span>

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