Compare commits

...

98 Commits

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

View File

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

29
go.mod
View File

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

69
go.sum
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -141,6 +141,23 @@ func (this *UpdateAction) RunPost(params struct {
this.Fail("验证码动作参数校验失败:" + err.Error())
}
// 检查极验配置
if captchaOptions.CaptchaType == firewallconfigs.CaptchaTypeGeeTest || captchaOptions.GeeTestConfig.IsOn {
if captchaOptions.CaptchaType == firewallconfigs.CaptchaTypeGeeTest && !captchaOptions.GeeTestConfig.IsOn {
this.Fail("人机识别动作配置的默认验证方式为极验-行为验,所以需要选择允许用户使用极验")
return
}
if len(captchaOptions.GeeTestConfig.CaptchaId) == 0 {
this.FailField("geetestCaptchaId", "请输入极验-验证ID")
return
}
if len(captchaOptions.GeeTestConfig.CaptchaKey) == 0 {
this.FailField("geetestCaptchaKey", "请输入极验-验证Key")
return
}
}
// 最大内容尺寸
if params.MaxRequestBodySize < 0 {
params.MaxRequestBodySize = 0

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -408,7 +408,8 @@ func (this *MySQLInstaller) Download() (path string, err error) {
// check latest version
this.log("checking mysql latest version ...")
var latestVersion = "8.1.0" // default version
var latestVersion = "8.2.0" // default version
var majorVersion = "8.2"
{
req, err := http.NewRequest(http.MethodGet, "https://dev.mysql.com/downloads/mysql/", nil)
if err != nil {
@@ -434,6 +435,10 @@ func (this *MySQLInstaller) Download() (path string, err error) {
var matches = reg.FindSubmatch(data)
if len(matches) > 0 {
latestVersion = string(matches[1])
var matchPieces = strings.Split(latestVersion, ".")
if len(matchPieces) >= 2 {
majorVersion = strings.Join(matchPieces[:2], ".")
}
}
}
}
@@ -441,7 +446,7 @@ func (this *MySQLInstaller) Download() (path string, err error) {
// download
this.log("start downloading ...")
var downloadURL = "https://cdn.mysql.com/Downloads/MySQL-8.1/mysql-" + latestVersion + "-linux-glibc2.17-x86_64-minimal.tar.xz"
var downloadURL = "https://cdn.mysql.com/Downloads/MySQL-" + majorVersion + "/mysql-" + latestVersion + "-linux-glibc2.17-x86_64-minimal.tar.xz"
{
this.log("downloading from url '" + downloadURL + "' ...")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -338,6 +338,9 @@ Vue.component("http-firewall-actions-box", {
this.editingIndex = index
this.actionCode = config.code
this.action = this.actions.$find(function (k, v) {
return v.code == config.code
})
switch (config.code) {
case "block":
@@ -771,7 +774,7 @@ Vue.component("http-firewall-actions-box", {
<input type="text" style="width: 5em" maxlength="9" v-model="captchaMaxFails" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">次</span>
</div>
<p class="comment">允许用户失败尝试的最多次数超过这个次数将被自动加入黑名单如果为空或者为0表示默认。</p>
<p class="comment"><span v-if="captchaMaxFails > 0 && captchaMaxFails < 5" class="red">建议填入一个不小于5的数字以减少误判几率。</span>允许用户失败尝试的最多次数超过这个次数将被自动加入黑名单如果为空或者为0表示默认。</p>
</td>
</tr>
<tr v-if="actionCode == 'captcha'">

View File

@@ -3,7 +3,11 @@ Vue.component("http-firewall-actions-view", {
props: ["v-actions"],
template: `<div>
<div v-for="action in vActions" style="margin-bottom: 0.3em">
<span :class="{red: action.category == 'block', orange: action.category == 'verify', green: action.category == 'allow'}">{{action.name}} ({{action.code.toUpperCase()}})</span>
<span :class="{red: action.category == 'block', orange: action.category == 'verify', green: action.category == 'allow'}">{{action.name}} ({{action.code.toUpperCase()}})
<div v-if="action.options != null">
<span class="grey small" v-if="action.code.toLowerCase() == 'page'">[{{action.options.status}}]</span>
</div>
</span>
</div>
</div>`
})

View File

@@ -25,7 +25,8 @@ Vue.component("http-firewall-captcha-options-viewer", {
}
return {
options: options,
summary: ""
summary: "",
captchaTypes: window.WAF_CAPTCHA_TYPES
}
},
methods: {
@@ -43,9 +44,24 @@ Vue.component("http-firewall-captcha-options-viewer", {
if (this.options.failBlockScopeAll) {
summaryList.push("全局封禁")
}
if (this.options.uiIsOn) {
summaryList.push("定制UI")
let that = this
let typeDef = this.captchaTypes.$find(function (k, v) {
return v.code == that.options.captchaType
})
if (typeDef != null) {
summaryList.push("默认验证方式:" + typeDef.name)
}
if (this.options.captchaType == "default") {
if (this.options.uiIsOn) {
summaryList.push("定制UI")
}
}
if (this.options.geeTestConfig != null && this.options.geeTestConfig.isOn) {
summaryList.push("已配置极验")
}
if (summaryList.length == 0) {
this.summary = "默认配置"
} else {

View File

@@ -7,6 +7,7 @@ Vue.component("http-firewall-captcha-options", {
let options = this.vCaptchaOptions
if (options == null) {
options = {
captchaType: "default",
countLetters: 0,
life: 0,
maxFails: 0,
@@ -21,17 +22,29 @@ Vue.component("http-firewall-captcha-options", {
uiFooter: "",
uiBody: "",
cookieId: "",
lang: ""
lang: "",
geeTestConfig: {
isOn: false,
captchaId: "",
captchaKey: ""
}
}
}
if (options.countLetters <= 0) {
options.countLetters = 6
}
if (options.captchaType == null || options.captchaType.length == 0) {
options.captchaType = "default"
}
return {
options: options,
isEditing: false,
summary: "",
uiBodyWarning: ""
uiBodyWarning: "",
captchaTypes: window.WAF_CAPTCHA_TYPES
}
},
watch: {
@@ -73,6 +86,9 @@ Vue.component("http-firewall-captcha-options", {
"options.failBlockScopeAll": function (v) {
this.updateSummary()
},
"options.captchaType": function (v) {
this.updateSummary()
},
"options.uiIsOn": function (v) {
this.updateSummary()
},
@@ -82,6 +98,9 @@ Vue.component("http-firewall-captcha-options", {
} else {
this.uiBodyWarning = ""
}
},
"options.geeTestConfig.isOn": function (v) {
this.updateSummary()
}
},
methods: {
@@ -102,9 +121,25 @@ Vue.component("http-firewall-captcha-options", {
if (this.options.failBlockScopeAll) {
summaryList.push("全局封禁")
}
if (this.options.uiIsOn) {
summaryList.push("定制UI")
let that = this
let typeDef = this.captchaTypes.$find(function (k, v) {
return v.code == that.options.captchaType
})
if (typeDef != null) {
summaryList.push("默认验证方式:" + typeDef.name)
}
if (this.options.captchaType == "default") {
if (this.options.uiIsOn) {
summaryList.push("定制UI")
}
}
if (this.options.geeTestConfig != null && this.options.geeTestConfig.isOn) {
summaryList.push("已配置极验")
}
if (summaryList.length == 0) {
this.summary = "默认配置"
} else {
@@ -121,6 +156,15 @@ Vue.component("http-firewall-captcha-options", {
<div v-show="isEditing" style="margin-top: 0.5em">
<table class="ui table definition selectable">
<tbody>
<tr>
<td>默认验证方式</td>
<td>
<select class="ui dropdown auto-width" v-model="options.captchaType">
<option v-for="captchaDef in captchaTypes" :value="captchaDef.code">{{captchaDef.name}}</option>
</select>
<p class="comment" v-for="captchaDef in captchaTypes" v-if="captchaDef.code == options.captchaType">{{captchaDef.description}}</p>
</td>
</tr>
<tr>
<td class="title">有效时间</td>
<td>
@@ -138,7 +182,7 @@ Vue.component("http-firewall-captcha-options", {
<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">允许用户失败尝试的最多次数超过这个次数将被自动加入黑名单。如果为空或者为0表示不限制。</p>
<p class="comment"><span v-if="options.maxFails > 0 && options.maxFails < 5" class="red">建议填入一个不小于5的数字以减少误判几率。</span>允许用户失败尝试的最多次数超过这个次数将被自动加入黑名单。如果为空或者为0表示不限制。</p>
</td>
</tr>
<tr>
@@ -158,7 +202,8 @@ Vue.component("http-firewall-captcha-options", {
<p class="comment">是否在失败时全局封禁,默认为只封禁对单个网站的访问。</p>
</td>
</tr>
<tr>
<tr v-show="options.captchaType == 'default'">
<td>验证码中数字个数</td>
<td>
<select class="ui dropdown auto-width" v-model="options.countLetters">
@@ -166,12 +211,12 @@ Vue.component("http-firewall-captcha-options", {
</select>
</td>
</tr>
<tr>
<tr v-show="options.captchaType == 'default'">
<td class="color-border">定制UI</td>
<td><checkbox v-model="options.uiIsOn"></checkbox></td>
</tr>
</tbody>
<tbody v-show="options.uiIsOn">
<tbody v-show="options.uiIsOn && options.captchaType == 'default'">
<tr>
<td class="color-border">页面标题</td>
<td>
@@ -221,6 +266,31 @@ Vue.component("http-firewall-captcha-options", {
</tr>
</tbody>
</table>
<table class="ui table definition selectable">
<tr>
<td class="title">允许用户使用极验</td>
<td><checkbox v-model="options.geeTestConfig.isOn"></checkbox>
<p class="comment">选中后表示允许用户在WAF设置中选择极验。</p>
</td>
</tr>
<tbody v-show="options.geeTestConfig.isOn">
<tr>
<td class="color-border">极验-验证ID *</td>
<td>
<input type="text" maxlength="100" name="geetestCaptchaId" v-model="options.geeTestConfig.captchaId" spellcheck="false"/>
<p class="comment">在极验控制台--业务管理中获取。</p>
</td>
</tr>
<tr>
<td class="color-border">极验-验证Key *</td>
<td>
<input type="text" maxlength="100" name="geetestCaptchaKey" v-model="options.geeTestConfig.captchaKey" spellcheck="false"/>
<p class="comment">在极验控制台--业务管理中获取。</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
`

View File

@@ -7,14 +7,34 @@ Vue.component("http-firewall-config-box", {
isPrior: false,
isOn: false,
firewallPolicyId: 0,
ignoreGlobalRules: false
ignoreGlobalRules: false,
defaultCaptchaType: "none"
}
}
if (firewall.defaultCaptchaType == null || firewall.defaultCaptchaType.length == 0) {
firewall.defaultCaptchaType = "none"
}
let allCaptchaTypes = window.WAF_CAPTCHA_TYPES.$copy()
// geetest
let geeTestIsOn = false
if (this.vFirewallPolicy != null && this.vFirewallPolicy.captchaAction != null && this.vFirewallPolicy.captchaAction.geeTestConfig != null) {
geeTestIsOn = this.vFirewallPolicy.captchaAction.geeTestConfig.isOn
}
// 如果没有启用geetest则还原
if (!geeTestIsOn && firewall.defaultCaptchaType == "geetest") {
firewall.defaultCaptchaType = "none"
}
return {
firewall: firewall,
moreOptionsVisible: false,
execGlobalRules: !firewall.ignoreGlobalRules
execGlobalRules: !firewall.ignoreGlobalRules,
captchaTypes: allCaptchaTypes,
geeTestIsOn: geeTestIsOn
}
},
watch: {
@@ -46,7 +66,7 @@ Vue.component("http-firewall-config-box", {
<prior-checkbox :v-config="firewall" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || firewall.isPrior">
<tr>
<td class="title">启用WAF</td>
<td class="title">启用Web防火墙</td>
<td>
<checkbox v-model="firewall.isOn"></checkbox>
<p class="comment">选中后表示启用当前网站的WAF功能。</p>
@@ -55,6 +75,17 @@ Vue.component("http-firewall-config-box", {
</tbody>
<more-options-tbody @change="changeOptionsVisible" v-show="firewall.isOn"></more-options-tbody>
<tbody v-show="moreOptionsVisible">
<tr>
<td>人机识别验证方式</td>
<td>
<select class="ui dropdown auto-width" v-model="firewall.defaultCaptchaType">
<option value="none">默认</option>
<option v-for="captchaType in captchaTypes" v-if="captchaType.code != 'geetest' || geeTestIsOn" :value="captchaType.code">{{captchaType.name}}</option>
</select>
<p class="comment" v-if="firewall.defaultCaptchaType == 'none'">使用系统默认的设置。</p>
<p class="comment" v-for="captchaType in captchaTypes" v-if="captchaType.code == firewall.defaultCaptchaType">{{captchaType.description}}</p>
</td>
</tr>
<tr>
<td>启用系统全局规则</td>
<td>

View File

@@ -22,6 +22,7 @@ Vue.component("http-firewall-policy-selector", {
select: function () {
let that = this
teaweb.popup("/servers/components/waf/selectPopup", {
height: "26em",
callback: function (resp) {
that.firewallPolicy = resp.data.firewallPolicy
}

View File

@@ -10,8 +10,32 @@ Vue.component("http-firewall-rule-label", {
showErr: function (err) {
teaweb.popupTip("规则校验错误,请修正:<span class=\"red\">" + teaweb.encodeHTML(err) + "</span>")
},
calculateParamName: function (param) {
let paramName = ""
if (param != null) {
window.WAF_RULE_CHECKPOINTS.forEach(function (checkpoint) {
if (param == "${" + checkpoint.prefix + "}" || param.startsWith("${" + checkpoint.prefix + ".")) {
paramName = checkpoint.name
}
})
}
return paramName
},
calculateParamDescription: function (param) {
let paramName = ""
let paramDescription = ""
if (param != null) {
window.WAF_RULE_CHECKPOINTS.forEach(function (checkpoint) {
if (param == "${" + checkpoint.prefix + "}" || param.startsWith("${" + checkpoint.prefix + ".")) {
paramName = checkpoint.name
paramDescription = checkpoint.description
}
})
}
return paramName + ": " + paramDescription
},
operatorName: function (operatorCode) {
var operatorName = operatorCode
let operatorName = operatorCode
if (typeof (window.WAF_RULE_OPERATORS) != null) {
window.WAF_RULE_OPERATORS.forEach(function (v) {
if (v.code == operatorCode) {
@@ -22,13 +46,39 @@ Vue.component("http-firewall-rule-label", {
return operatorName
},
operatorDescription: function (operatorCode) {
let operatorName = operatorCode
let operatorDescription = ""
if (typeof (window.WAF_RULE_OPERATORS) != null) {
window.WAF_RULE_OPERATORS.forEach(function (v) {
if (v.code == operatorCode) {
operatorName = v.name
operatorDescription = v.description
}
})
}
return operatorName + ": " + operatorDescription
},
operatorDataType: function (operatorCode) {
let operatorDataType = "none"
if (typeof (window.WAF_RULE_OPERATORS) != null) {
window.WAF_RULE_OPERATORS.forEach(function (v) {
if (v.code == operatorCode) {
operatorDataType = v.dataType
}
})
}
return operatorDataType
},
isEmptyString: function (v) {
return typeof v == "string" && v.length == 0
}
},
template: `<div>
<div class="ui label tiny basic" style="line-height: 1.5">
{{rule.name}}[{{rule.param}}]
<div class="ui label small basic" style="line-height: 1.5">
{{rule.name}} <span :title="calculateParamDescription(rule.param)" class="hover">{{calculateParamName(rule.param)}}<span class="small grey"> {{rule.param}}</span></span>
<!-- cc2 -->
<span v-if="rule.param == '\${cc2}'">
@@ -43,9 +93,9 @@ Vue.component("http-firewall-rule-label", {
<span v-else>
<span v-if="rule.paramFilters != null && rule.paramFilters.length > 0" v-for="paramFilter in rule.paramFilters"> | {{paramFilter.code}}</span>
<span :class="{dash:!rule.isComposed && rule.isCaseInsensitive}" :title="(!rule.isComposed && rule.isCaseInsensitive) ? '大小写不敏感':''">{{operatorName(rule.operator)}}</span>
<span v-if="!isEmptyString(rule.value)">{{rule.value}}</span>
<span v-else class="disabled" style="font-weight: normal" title="空字符串">[空]</span>
<span class="hover" :class="{dash:!rule.isComposed && rule.isCaseInsensitive}" :title="operatorDescription(rule.operator) + ((!rule.isComposed && rule.isCaseInsensitive) ? '\\n[大小写不敏感] ':'')">&lt;{{operatorName(rule.operator)}}&gt;</span>
<span class="hover" v-if="!isEmptyString(rule.value)">{{rule.value}}</span>
<span v-else-if="operatorDataType(rule.operator) != 'none'" class="disabled" style="font-weight: normal" title="空字符串">[空]</span>
</span>
<!-- description -->

View File

@@ -37,7 +37,7 @@ Vue.component("http-firewall-rules-box", {
})
},
operatorName: function (operatorCode) {
var operatorName = operatorCode
let operatorName = operatorCode
if (typeof (window.WAF_RULE_OPERATORS) != null) {
window.WAF_RULE_OPERATORS.forEach(function (v) {
if (v.code == operatorCode) {
@@ -48,6 +48,56 @@ Vue.component("http-firewall-rules-box", {
return operatorName
},
operatorDescription: function (operatorCode) {
let operatorName = operatorCode
let operatorDescription = ""
if (typeof (window.WAF_RULE_OPERATORS) != null) {
window.WAF_RULE_OPERATORS.forEach(function (v) {
if (v.code == operatorCode) {
operatorName = v.name
operatorDescription = v.description
}
})
}
return operatorName + ": " + operatorDescription
},
operatorDataType: function (operatorCode) {
let operatorDataType = "none"
if (typeof (window.WAF_RULE_OPERATORS) != null) {
window.WAF_RULE_OPERATORS.forEach(function (v) {
if (v.code == operatorCode) {
operatorDataType = v.dataType
}
})
}
return operatorDataType
},
calculateParamName: function (param) {
let paramName = ""
if (param != null) {
window.WAF_RULE_CHECKPOINTS.forEach(function (checkpoint) {
if (param == "${" + checkpoint.prefix + "}" || param.startsWith("${" + checkpoint.prefix + ".")) {
paramName = checkpoint.name
}
})
}
return paramName
},
calculateParamDescription: function (param) {
let paramName = ""
let paramDescription = ""
if (param != null) {
window.WAF_RULE_CHECKPOINTS.forEach(function (checkpoint) {
if (param == "${" + checkpoint.prefix + "}" || param.startsWith("${" + checkpoint.prefix + ".")) {
paramName = checkpoint.name
paramDescription = checkpoint.description
}
})
}
return paramName + ": " + paramDescription
},
isEmptyString: function (v) {
return typeof v == "string" && v.length == 0
}
@@ -56,7 +106,7 @@ Vue.component("http-firewall-rules-box", {
<input type="hidden" name="rulesJSON" :value="JSON.stringify(rules)"/>
<div v-if="rules.length > 0">
<div v-for="(rule, index) in rules" class="ui label small basic" style="margin-bottom: 0.5em; line-height: 1.5">
{{rule.name}}[{{rule.param}}]
{{rule.name}} <span :title="calculateParamDescription(rule.param)" class="hover">{{calculateParamName(rule.param)}}<span class="small grey"> {{rule.param}}</span></span>
<!-- cc2 -->
<span v-if="rule.param == '\${cc2}'">
@@ -70,9 +120,9 @@ Vue.component("http-firewall-rules-box", {
</span>
<span v-else>
<span v-if="rule.paramFilters != null && rule.paramFilters.length > 0" v-for="paramFilter in rule.paramFilters"> | {{paramFilter.code}}</span> <span :class="{dash:(!rule.isComposed && rule.isCaseInsensitive)}" :title="(!rule.isComposed && rule.isCaseInsensitive) ? '大小写不敏感':''">{{operatorName(rule.operator)}}</span>
<span v-if="!isEmptyString(rule.value)">{{rule.value}}</span>
<span v-else class="disabled" style="font-weight: normal" title="空字符串">[空]</span>
<span v-if="rule.paramFilters != null && rule.paramFilters.length > 0" v-for="paramFilter in rule.paramFilters"> | {{paramFilter.code}}</span> <span class="hover" :title="operatorDescription(rule.operator) + ((!rule.isComposed && rule.isCaseInsensitive) ? '\\n[大小写不敏感] ':'')">&lt;{{operatorName(rule.operator)}}&gt;</span>
<span v-if="!isEmptyString(rule.value)" class="hover">{{rule.value}}</span>
<span v-else-if="operatorDataType(rule.operator) != 'none'" class="disabled" style="font-weight: normal" title="空字符串">[空]</span>
</span>
<!-- description -->

View File

@@ -93,6 +93,7 @@ Vue.component("http-header-policy-box", {
},
addSettingHeader: function (policyId) {
teaweb.popup("/servers/server/settings/headers/createSetPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&type=" + this.type, {
height: "22em",
callback: function () {
teaweb.successRefresh("保存成功")
}
@@ -114,6 +115,7 @@ Vue.component("http-header-policy-box", {
},
updateSettingPopup: function (policyId, headerId) {
teaweb.popup("/servers/server/settings/headers/updateSetPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&headerId=" + headerId + "&type=" + this.type, {
height: "22em",
callback: function () {
teaweb.successRefresh("保存成功")
}

View File

@@ -45,7 +45,7 @@ Vue.component("http-host-redirect-box", {
teaweb.popup("/servers/server/settings/redirects/createPopup", {
width: "50em",
height: "30em",
height: "36em",
callback: function (resp) {
that.id++
resp.data.redirect.id = that.id
@@ -60,7 +60,7 @@ Vue.component("http-host-redirect-box", {
teaweb.popup("/servers/server/settings/redirects/createPopup", {
width: "50em",
height: "30em",
height: "36em",
callback: function (resp) {
resp.data.redirect.id = redirect.id
Vue.set(that.redirects, index, resp.data.redirect)
@@ -119,6 +119,8 @@ Vue.component("http-host-redirect-box", {
<grey-label v-if="redirect.matchPrefix">匹配前缀</grey-label>
<grey-label v-if="redirect.matchRegexp">正则匹配</grey-label>
<grey-label v-if="!redirect.matchPrefix && !redirect.matchRegexp">精准匹配</grey-label>
<grey-label v-if="redirect.exceptDomains != null && redirect.exceptDomains.length > 0" v-for="domain in redirect.exceptDomains">排除:{{domain}}</grey-label>
<grey-label v-if="redirect.onlyDomains != null && redirect.onlyDomains.length > 0" v-for="domain in redirect.onlyDomains">仅限:{{domain}}</grey-label>
</div>
</div>
<div v-if="redirect.type == 'domain'">

View File

@@ -48,29 +48,32 @@ Vue.component("http-pages-and-shutdown-box", {
addPage: function () {
let that = this
teaweb.popup("/servers/server/settings/pages/createPopup", {
height: "26em",
height: "30em",
callback: function (resp) {
that.pages.push(resp.data.page)
that.notifyChange()
}
})
},
updatePage: function (pageIndex, pageId) {
let that = this
teaweb.popup("/servers/server/settings/pages/updatePopup?pageId=" + pageId, {
height: "26em",
height: "30em",
callback: function (resp) {
Vue.set(that.pages, pageIndex, resp.data.page)
that.notifyChange()
}
})
},
removePage: function (pageIndex) {
let that = this
teaweb.confirm("确定要除此页面吗?", function () {
teaweb.confirm("确定要除此自定义页面吗?", function () {
that.pages.$remove(pageIndex)
that.notifyChange()
})
},
addShutdownHTMLTemplate: function () {
this.shutdownConfig.body = `<!DOCTYPE html>
this.shutdownConfig.body = `<!DOCTYPE html>
<html lang="en">
<head>
\t<title>升级中</title>
@@ -89,80 +92,157 @@ Vue.component("http-pages-and-shutdown-box", {
</body>
</html>`
},
notifyChange: function () {
let parent = this.$el.parentNode
while (true) {
if (parent == null) {
break
}
if (parent.tagName == "FORM") {
break
}
parent = parent.parentNode
}
if (parent != null) {
setTimeout(function () {
Tea.runActionOn(parent)
}, 100)
}
}
},
template: `<div>
<input type="hidden" name="pagesJSON" :value="JSON.stringify(pages)"/>
<input type="hidden" name="shutdownJSON" :value="JSON.stringify(shutdownConfig)"/>
<table class="ui table selectable definition">
<tr>
<td class="title">自定义页面</td>
<td>
<div v-if="pages.length > 0">
<div class="ui label small basic" v-for="(page,index) in pages">
{{page.status}} -&gt; <span v-if="page.bodyType == 'url'">{{page.url}}</span><span v-if="page.bodyType == 'html'">[HTML内容]</span> <a href="" title="修改" @click.prevent="updatePage(index, page.id)"><i class="icon pencil small"></i></a> <a href="" title="删除" @click.prevent="removePage(index)"><i class="icon remove"></i></a>
<h4 style="margin-bottom: 0.5em">自定义页面</h4>
<p class="comment" style="padding-top: 0; margin-top: 0">根据响应状态码返回一些自定义页面比如404500等错误页面。</p>
<div v-if="pages.length > 0">
<table class="ui table selectable celled">
<thead>
<tr>
<th class="two wide">响应状态码</th>
<th>页面类型</th>
<th class="two wide">新状态码</th>
<th>例外URL</th>
<th>限制URL</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="(page,index) in pages">
<td>
<a href="" @click.prevent="updatePage(index, page.id)">
<span v-if="page.status != null && page.status.length == 1">{{page.status[0]}}</span>
<span v-else>{{page.status}}</span>
<i class="icon expand small"></i>
</a>
</td>
<td style="word-break: break-all">
<div v-if="page.bodyType == 'url'">
{{page.url}}
<div>
<grey-label>读取URL</grey-label>
</div>
</div>
<div class="ui divider"></div>
</div>
<div>
<button class="ui button small" type="button" @click.prevent="addPage()">+</button>
</div>
<p class="comment">根据响应状态码返回一些自定义页面比如404500等错误页面。</p>
</td>
</tr>
<tr>
<td>临时关闭页面</td>
<td>
<div>
<table class="ui table selectable definition">
<prior-checkbox :v-config="shutdownConfig" v-if="vIsLocation"></prior-checkbox>
<tbody v-show="!vIsLocation || shutdownConfig.isPrior">
<tr>
<td class="title">临时关闭网站</td>
<td>
<div class="ui checkbox">
<input type="checkbox" value="1" v-model="shutdownConfig.isOn" />
<label></label>
</div>
<p class="comment">选中后,表示临时关闭当前网站,并显示自定义内容。</p>
</td>
</tr>
</tbody>
<tbody v-show="(!vIsLocation || shutdownConfig.isPrior) && shutdownConfig.isOn">
<tr>
<td>显示内容类型 *</td>
<td>
<select class="ui dropdown auto-width" v-model="shutdownConfig.bodyType">
<option value="html">HTML</option>
<option value="url">读取URL</option>
</select>
</td>
</tr>
<tr v-show="shutdownConfig.bodyType == 'url'">
<td class="title">显示页面URL *</td>
<td>
<input type="text" v-model="shutdownConfig.url" placeholder="类似于 https://example.com/page.html"/>
<p class="comment">将从此URL中读取内容。</p>
</td>
</tr>
<tr v-show="shutdownConfig.bodyType == 'html'">
<td>显示页面HTML *</td>
<td>
<textarea name="body" ref="shutdownHTMLBody" v-model="shutdownConfig.body"></textarea>
<p class="comment"><a href="" @click.prevent="addShutdownHTMLTemplate">[使用模板]</a>。填写页面的HTML内容支持请求变量。</p>
</td>
</tr>
<tr>
<td>状态码</td>
<td><input type="text" size="3" maxlength="3" name="shutdownStatus" style="width:5.2em" placeholder="状态码" v-model="shutdownStatus"/></td>
</tr>
</tbody>
</table>
<p class="comment">开启临时关闭页面时,所有请求都会直接显示此页面。可用于临时升级网站或者禁止用户访问某个网页。</p>
</div>
</td>
</tr>
</table>
<div v-if="page.bodyType == 'redirectURL'">
{{page.url}}
<div>
<grey-label>跳转URL</grey-label>
<grey-label v-if="page.newStatus > 0">{{page.newStatus}}</grey-label>
</div>
</div>
<div v-if="page.bodyType == 'html'">
[HTML内容]
<div>
<grey-label v-if="page.newStatus > 0">{{page.newStatus}}</grey-label>
</div>
</div>
</td>
<td>
<span v-if="page.newStatus > 0">{{page.newStatus}}</span>
<span v-else class="disabled">保持</span>
</td>
<td>
<div v-if="page.exceptURLPatterns != null && page.exceptURLPatterns">
<span v-for="urlPattern in page.exceptURLPatterns" class="ui basic label small">{{urlPattern.pattern}}</span>
</div>
<span v-else class="disabled">-</span>
</td>
<td>
<div v-if="page.onlyURLPatterns != null && page.onlyURLPatterns">
<span v-for="urlPattern in page.onlyURLPatterns" class="ui basic label small">{{urlPattern.pattern}}</span>
</div>
<span v-else class="disabled">-</span>
</td>
<td>
<a href="" title="修改" @click.prevent="updatePage(index, page.id)">修改</a> &nbsp;
<a href="" title="删除" @click.prevent="removePage(index)">删除</a>
</td>
</tr>
</table>
</div>
<div style="margin-top: 1em">
<button class="ui button small" type="button" @click.prevent="addPage()">+添加自定义页面</button>
</div>
<h4 style="margin-top: 2em;">临时关闭页面</h4>
<p class="comment" style="margin-top: 0; padding-top: 0">开启临时关闭页面时,所有请求都会直接显示此页面。可用于临时升级网站或者禁止用户访问某个网页。</p>
<div>
<table class="ui table selectable definition">
<prior-checkbox :v-config="shutdownConfig" v-if="vIsLocation"></prior-checkbox>
<tbody v-show="!vIsLocation || shutdownConfig.isPrior">
<tr>
<td class="title">启用临时关闭网站</td>
<td>
<div class="ui checkbox">
<input type="checkbox" value="1" v-model="shutdownConfig.isOn" />
<label></label>
</div>
<p class="comment">选中后,表示临时关闭当前网站,并显示自定义内容。</p>
</td>
</tr>
</tbody>
<tbody v-show="(!vIsLocation || shutdownConfig.isPrior) && shutdownConfig.isOn">
<tr>
<td>显示内容类型 *</td>
<td>
<select class="ui dropdown auto-width" v-model="shutdownConfig.bodyType">
<option value="html">HTML</option>
<option value="url">读取URL</option>
<option value="redirectURL">跳转URL</option>
</select>
</td>
</tr>
<tr v-if="shutdownConfig.bodyType == 'url'">
<td class="title">显示页面URL *</td>
<td>
<input type="text" v-model="shutdownConfig.url" placeholder="类似于 https://example.com/page.html"/>
<p class="comment">将从此URL中读取内容。</p>
</td>
</tr>
<tr v-if="shutdownConfig.bodyType == 'redirectURL'">
<td class="title">跳转到URL *</td>
<td>
<input type="text" v-model="shutdownConfig.url" placeholder="类似于 https://example.com/page.html"/>
<p class="comment">将会跳转到此URL。</p>
</td>
</tr>
<tr v-show="shutdownConfig.bodyType == 'html'">
<td>显示页面HTML *</td>
<td>
<textarea name="body" ref="shutdownHTMLBody" v-model="shutdownConfig.body"></textarea>
<p class="comment"><a href="" @click.prevent="addShutdownHTMLTemplate">[使用模板]</a>。填写页面的HTML内容支持请求变量。</p>
</td>
</tr>
<tr>
<td>状态码</td>
<td><input type="text" size="3" maxlength="3" name="shutdownStatus" style="width:5.2em" placeholder="状态码" v-model="shutdownStatus"/></td>
</tr>
</tbody>
</table>
</div>
<div class="ui margin"></div>
</div>`
})

View File

@@ -17,6 +17,7 @@ Vue.component("http-pages-box", {
height: "26em",
callback: function (resp) {
that.pages.push(resp.data.page)
that.notifyChange()
}
})
},
@@ -26,6 +27,7 @@ Vue.component("http-pages-box", {
height: "26em",
callback: function (resp) {
Vue.set(that.pages, pageIndex, resp.data.page)
that.notifyChange()
}
})
},
@@ -33,28 +35,98 @@ Vue.component("http-pages-box", {
let that = this
teaweb.confirm("确定要移除此页面吗?", function () {
that.pages.$remove(pageIndex)
that.notifyChange()
})
},
notifyChange: function () {
let parent = this.$el.parentNode
while (true) {
if (parent == null) {
break
}
if (parent.tagName == "FORM") {
break
}
parent = parent.parentNode
}
if (parent != null) {
setTimeout(function () {
Tea.runActionOn(parent)
}, 100)
}
}
},
template: `<div>
<input type="hidden" name="pagesJSON" :value="JSON.stringify(pages)"/>
<table class="ui table selectable definition">
<tr>
<td class="title">自定义页面</td>
<td>
<div v-if="pages.length > 0">
<div class="ui label small basic" v-for="(page,index) in pages">
{{page.status}} -&gt; <span v-if="page.bodyType == 'url'">{{page.url}}</span><span v-if="page.bodyType == 'html'">[HTML内容]</span> <a href="" title="修改" @click.prevent="updatePage(index, page.id)"><i class="icon pencil small"></i></a> <a href="" title="删除" @click.prevent="removePage(index)"><i class="icon remove"></i></a>
<div v-if="pages.length > 0">
<table class="ui table selectable celled">
<thead>
<tr>
<th class="two wide">响应状态码</th>
<th>页面类型</th>
<th class="two wide">新状态码</th>
<th>例外URL</th>
<th>限制URL</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="(page,index) in pages">
<td>
<a href="" @click.prevent="updatePage(index, page.id)">
<span v-if="page.status != null && page.status.length == 1">{{page.status[0]}}</span>
<span v-else>{{page.status}}</span>
<i class="icon expand small"></i>
</a>
</td>
<td style="word-break: break-all">
<div v-if="page.bodyType == 'url'">
{{page.url}}
<div>
<grey-label>读取URL</grey-label>
</div>
</div>
<div class="ui divider"></div>
</div>
<div>
<button class="ui button small" type="button" @click.prevent="addPage()">+</button>
</div>
<p class="comment">根据响应状态码返回一些自定义页面比如404500等错误页面。</p>
</td>
</tr>
</table>
<div v-if="page.bodyType == 'redirectURL'">
{{page.url}}
<div>
<grey-label>跳转URL</grey-label>
<grey-label v-if="page.newStatus > 0">{{page.newStatus}}</grey-label>
</div>
</div>
<div v-if="page.bodyType == 'html'">
[HTML内容]
<div>
<grey-label v-if="page.newStatus > 0">{{page.newStatus}}</grey-label>
</div>
</div>
</td>
<td>
<span v-if="page.newStatus > 0">{{page.newStatus}}</span>
<span v-else class="disabled">保持</span>
</td>
<td>
<div v-if="page.exceptURLPatterns != null && page.exceptURLPatterns">
<span v-for="urlPattern in page.exceptURLPatterns" class="ui basic label small">{{urlPattern.pattern}}</span>
</div>
<span v-else class="disabled">-</span>
</td>
<td>
<div v-if="page.onlyURLPatterns != null && page.onlyURLPatterns">
<span v-for="urlPattern in page.onlyURLPatterns" class="ui basic label small">{{urlPattern.pattern}}</span>
</div>
<span v-else class="disabled">-</span>
</td>
<td>
<a href="" title="修改" @click.prevent="updatePage(index, page.id)">修改</a> &nbsp;
<a href="" title="删除" @click.prevent="removePage(index)">删除</a>
</td>
</tr>
</table>
</div>
<div style="margin-top: 1em">
<button class="ui button small" type="button" @click.prevent="addPage()">+添加自定义页面</button>
</div>
<div class="ui margin"></div>
</div>`
})

View File

@@ -6,7 +6,6 @@ Vue.component("http-webp-config-box", {
config = {
isPrior: false,
isOn: false,
quality: 50,
minLength: {count: 0, "unit": "kb"},
maxLength: {count: 0, "unit": "kb"},
mimeTypes: ["image/png", "image/jpeg", "image/bmp", "image/x-ico"],
@@ -24,21 +23,7 @@ Vue.component("http-webp-config-box", {
return {
config: config,
moreOptionsVisible: false,
quality: config.quality
}
},
watch: {
quality: function (v) {
let quality = parseInt(v)
if (isNaN(quality)) {
quality = 90
} else if (quality < 1) {
quality = 1
} else if (quality > 100) {
quality = 100
}
this.config.quality = quality
moreOptionsVisible: false
}
},
methods: {
@@ -95,16 +80,6 @@ Vue.component("http-webp-config-box", {
<p class="comment">响应的Content-Type里包含这些MimeType的内容将会被转成WebP。</p>
</td>
</tr>
<tr>
<td>图片质量</td>
<td>
<div class="ui input right labeled">
<input type="text" v-model="quality" style="width: 5em" maxlength="4"/>
<span class="ui label">%</span>
</div>
<p class="comment">取值在0到100之间数值越大生成的图像越清晰同时文件尺寸也会越大。</p>
</td>
</tr>
<tr>
<td>内容最小长度</td>
<td>

View File

@@ -1,6 +1,6 @@
// 指标图表
Vue.component("metric-chart", {
props: ["v-chart", "v-stats", "v-item"],
props: ["v-chart", "v-stats", "v-item", "v-column" /** in column? **/],
mounted: function () {
this.load()
},
@@ -188,7 +188,7 @@ Vue.component("metric-chart", {
color: teaweb.DefaultChartColor
},
areaStyle: {},
barWidth: "20em"
barWidth: "10em"
}
]
})
@@ -346,7 +346,7 @@ Vue.component("metric-chart", {
color: teaweb.DefaultChartColor
},
areaStyle: {},
barWidth: "20em"
barWidth: "10em"
}
]
})
@@ -414,7 +414,7 @@ Vue.component("metric-chart", {
return time
}
},
template: `<div style="float: left" :style="{'width': width}">
template: `<div style="float: left" :style="{'width': this.vColumn ? '' : width}" :class="{'ui column':this.vColumn}">
<h4>{{chart.name}} <span>{{valueTypeName}}</span></h4>
<div class="ui divider"></div>
<div style="height: 14em; padding-bottom: 1em; " :id="chartId" :class="{'scroll-box': chart.type == 'table'}"></div>

View File

@@ -26,7 +26,8 @@ Vue.component("reverse-proxy-box", {
maxConns: 0,
maxIdleConns: 0,
followRedirects: false,
retry50X: true
retry50X: false,
retry40X: false
}
}
if (reverseProxyConfig.addHeaders == null) {
@@ -236,6 +237,38 @@ Vue.component("reverse-proxy-box", {
<p class="comment">开启后将自动刷新缓冲区数据到客户端在类似于SSEserver-sent events等场景下很有用。</p>
</td>
</tr>
<tr v-show="family == null || family == 'http'">
<td>自动重试50X</td>
<td>
<checkbox v-model="reverseProxyConfig.retry50X"></checkbox>
<p class="comment">选中后表示当源站返回状态码为50X比如502、504等自动重试其他源站。</p>
</td>
</tr>
<tr v-show="family == null || family == 'http'">
<td>自动重试40X</td>
<td>
<checkbox v-model="reverseProxyConfig.retry40X"></checkbox>
<p class="comment">选中后表示当源站返回状态码为40X403或404自动重试其他源站。</p>
</td>
</tr>
<tr v-show="family != 'unix'">
<td>PROXY Protocol</td>
<td>
<checkbox name="proxyProtocolIsOn" v-model="reverseProxyConfig.proxyProtocol.isOn"></checkbox>
<p class="comment">选中后表示启用PROXY Protocol每次连接源站时都会在头部写入客户端地址信息。</p>
</td>
</tr>
<tr v-show="family != 'unix' && reverseProxyConfig.proxyProtocol.isOn">
<td>PROXY Protocol版本</td>
<td>
<select class="ui dropdown auto-width" name="proxyProtocolVersion" v-model="reverseProxyConfig.proxyProtocol.version">
<option value="1">1</option>
<option value="2">2</option>
</select>
<p class="comment" v-if="reverseProxyConfig.proxyProtocol.version == 1">发送类似于<code-label>PROXY TCP4 192.168.1.1 192.168.1.10 32567 443</code-label>的头部信息。</p>
<p class="comment" v-if="reverseProxyConfig.proxyProtocol.version == 2">发送二进制格式的头部信息。</p>
</td>
</tr>
<tr v-if="family == null || family == 'http'">
<td class="color-border">源站连接失败超时时间</td>
<td>
@@ -300,31 +333,6 @@ Vue.component("reverse-proxy-box", {
<p class="comment">源站保持等待的空闲超时时间0表示使用默认时间。</p>
</td>
</tr>
<tr v-show="family == null || family == 'http'">
<td>自动重试50X</td>
<td>
<checkbox v-model="reverseProxyConfig.retry50X"></checkbox>
<p class="comment">选中后表示当源站返回状态码为50X比如502、504自动重试。</p>
</td>
</tr>
<tr v-show="family != 'unix'">
<td>PROXY Protocol</td>
<td>
<checkbox name="proxyProtocolIsOn" v-model="reverseProxyConfig.proxyProtocol.isOn"></checkbox>
<p class="comment">选中后表示启用PROXY Protocol每次连接源站时都会在头部写入客户端地址信息。</p>
</td>
</tr>
<tr v-show="family != 'unix' && reverseProxyConfig.proxyProtocol.isOn">
<td>PROXY Protocol版本</td>
<td>
<select class="ui dropdown auto-width" name="proxyProtocolVersion" v-model="reverseProxyConfig.proxyProtocol.version">
<option value="1">1</option>
<option value="2">2</option>
</select>
<p class="comment" v-if="reverseProxyConfig.proxyProtocol.version == 1">发送类似于<code-label>PROXY TCP4 192.168.1.1 192.168.1.10 32567 443</code-label>的头部信息。</p>
<p class="comment" v-if="reverseProxyConfig.proxyProtocol.version == 2">发送二进制格式的头部信息。</p>
</td>
</tr>
</tbody>
</table>
<div class="margin"></div>

View File

@@ -1,12 +1,34 @@
Vue.component("script-config-box", {
props: ["id", "v-script-config", "comment"],
props: ["id", "v-script-config", "comment", "v-auditing-status"],
mounted: function () {
let that = this
setTimeout(function () {
that.$forceUpdate()
}, 100)
},
data: function () {
let config = this.vScriptConfig
if (config == null) {
config = {
isPrior: false,
isOn: false,
code: ""
code: "",
auditingCode: ""
}
}
let auditingStatus = null
if (config.auditingCodeMD5 != null && config.auditingCodeMD5.length > 0 && config.auditingCode != null && config.auditingCode.length > 0) {
config.code = config.auditingCode
if (this.vAuditingStatus != null) {
for (let i = 0; i < this.vAuditingStatus.length; i++) {
let status = this.vAuditingStatus[i]
if (status.md5 == config.auditingCodeMD5) {
auditingStatus = status
break
}
}
}
}
@@ -15,7 +37,8 @@ Vue.component("script-config-box", {
}
return {
config: config
config: config,
auditingStatus: auditingStatus
}
},
watch: {
@@ -30,6 +53,12 @@ Vue.component("script-config-box", {
changeCode: function (code) {
this.config.code = code
this.change()
},
isPlus: function () {
if (Tea == null || Tea.Vue == null) {
return false
}
return Tea.Vue.teaIsPlus
}
},
template: `<div>
@@ -43,7 +72,14 @@ Vue.component("script-config-box", {
<tbody>
<tr :style="{opacity: !config.isOn ? 0.5 : 1}">
<td>脚本代码</td>
<td><source-code-box :id="id" type="text/javascript" :read-only="false" @change="changeCode">{{config.code}}</source-code-box>
<td>
<p class="comment" v-if="auditingStatus != null">
<span class="green" v-if="auditingStatus.isPassed">管理员审核结果:审核通过。</span>
<span class="red" v-else-if="auditingStatus.isRejected">管理员审核结果:驳回 &nbsp; &nbsp; 驳回理由:{{auditingStatus.rejectedReason}}</span>
<span class="red" v-else>当前脚本将在审核后生效,请耐心等待审核结果。 <a href="/servers/user-scripts" target="_blank" v-if="isPlus()">去审核 &raquo;</a></span>
</p>
<p class="comment" v-if="auditingStatus == null"><span class="green">管理员审核结果:审核通过。</span></p>
<source-code-box :id="id" type="text/javascript" :read-only="false" @change="changeCode">{{config.code}}</source-code-box>
<p class="comment">{{comment}}</p>
</td>
</tr>

View File

@@ -1,5 +1,5 @@
Vue.component("script-group-config-box", {
props: ["v-group", "v-is-location"],
props: ["v-group", "v-auditing-status", "v-is-location"],
data: function () {
let group = this.vGroup
if (group == null) {
@@ -37,7 +37,7 @@ Vue.component("script-group-config-box", {
<prior-checkbox :v-config="group" v-if="vIsLocation"></prior-checkbox>
</table>
<div :style="{opacity: (!vIsLocation || group.isPrior) ? 1 : 0.5}">
<script-config-box :v-script-config="script" comment="在接收到客户端请求之后立即调用。预置req、resp变量。" @change="changeScript" :v-is-location="vIsLocation"></script-config-box>
<script-config-box :v-script-config="script" :v-auditing-status="vAuditingStatus" comment="在接收到客户端请求之后立即调用。预置req、resp变量。" @change="changeScript" :v-is-location="vIsLocation"></script-config-box>
</div>
</div>`
})

View File

@@ -0,0 +1,32 @@
Vue.component("server-traffic-limit-status-viewer", {
props: ["value"],
data: function () {
let targetTypeName = "流量"
if (this.value != null) {
targetTypeName = this.targetTypeToName(this.value.targetType)
}
return {
status: this.value,
targetTypeName: targetTypeName
}
},
methods: {
targetTypeToName: function (targetType) {
switch (targetType) {
case "traffic":
return "流量"
case "request":
return "请求数"
case "websocketConnections":
return "Websocket连接数"
}
return "流量"
}
},
template: `<span v-if="status != null">
<span v-if="status.dateType == 'day'" class="small red">已达到<span v-if="status.planId > 0">套餐</span>当日{{targetTypeName}}限制</span>
<span v-if="status.dateType == 'month'" class="small red">已达到<span v-if="status.planId > 0">套餐</span>当月{{targetTypeName}}限制</span>
<span v-if="status.dateType == 'total'" class="small red">已达到<span v-if="status.planId > 0">套餐</span>总体{{targetTypeName}}限制</span>
</span>`
})

View File

@@ -0,0 +1,2 @@
// generated by 'langs generate'
window.LANG_MESSAGES_BASE = {"admin_dashboard@ui_dns":"DNS","admin_dashboard@ui_events":"事件","admin_dashboard@ui_overview":"概况","admin_dashboard@ui_user":"用户","admin_dashboard@ui_waf":"WAF"};

View File

@@ -0,0 +1,14 @@
.main-menu .menu .item,
.left-box .menu .item {
font-size: 0.95em;
}
.main-menu .menu .sub-items .item,
.left-box .menu .sub-items .item {
line-height: 1.4;
}
.left-box .menu .item {
line-height: 1.4;
padding-top: 0.2em !important;
padding-bottom: 0.2em !important;
}
/*# sourceMappingURL=en-us.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["en-us.less"],"names":[],"mappings":"AAAA,UACC,MACC;AAFU,SACX,MACC;EACC,iBAAA;;AAHH,UACC,MAKC,WACC;AAPS,SACX,MAKC,WACC;EACC,gBAAA;;AAMJ,SACC,MACC;EACC,gBAAA;EACA,kBAAA;EACA,qBAAA","file":"en-us.css"}

View File

@@ -0,0 +1,2 @@
// generated by 'langs generate'
window.LANG_MESSAGES = {"admin_dashboard@ui_dns":"DNS","admin_dashboard@ui_events":"Events","admin_dashboard@ui_overview":"Overview","admin_dashboard@ui_user":"Users","admin_dashboard@ui_waf":"WAF"};

View File

@@ -0,0 +1,23 @@
.main-menu, .left-box {
.menu {
.item {
font-size: 0.95em;
}
.sub-items {
.item {
line-height: 1.4;
}
}
}
}
.left-box {
.menu {
.item {
line-height: 1.4;
padding-top: 0.2em !important;
padding-bottom: 0.2em !important;
}
}
}

View File

View File

@@ -124,21 +124,21 @@ window.teaweb = {
return bytes + "B";
}
if (bytes < Math.pow(1024, 2)) {
return (Math.round(bytes * 100 / Math.pow(1024, 1)) / 100) + "KB";
return (Math.round(bytes * 100 / Math.pow(1024, 1)) / 100) + "KiB";
}
if (bytes < Math.pow(1024, 3)) {
return (Math.round(bytes * 100 / Math.pow(1024, 2)) / 100) + "MB";
return (Math.round(bytes * 100 / Math.pow(1024, 2)) / 100) + "MiB";
}
if (bytes < Math.pow(1024, 4)) {
return (Math.round(bytes * 100 / Math.pow(1024, 3)) / 100) + "GB";
return (Math.round(bytes * 100 / Math.pow(1024, 3)) / 100) + "GiB";
}
if (bytes < Math.pow(1024, 5)) {
return (Math.round(bytes * 100 / Math.pow(1024, 4)) / 100) + "TB";
return (Math.round(bytes * 100 / Math.pow(1024, 4)) / 100) + "TiB";
}
if (bytes < Math.pow(1024, 6)) {
return (Math.round(bytes * 100 / Math.pow(1024, 5)) / 100) + "PB";
return (Math.round(bytes * 100 / Math.pow(1024, 5)) / 100) + "PiB";
}
return (Math.round(bytes * 100 / Math.pow(1024, 6)) / 100) + "EB";
return (Math.round(bytes * 100 / Math.pow(1024, 6)) / 100) + "EiB";
},
formatBits: function (bits, decimal) {
bits = Math.ceil(bits);
@@ -592,7 +592,8 @@ window.teaweb = {
itemStyle: {
color: this.DefaultChartColor
},
barWidth: "20em"
barWidth: "10em",
areaStyle: {}
}
],
animation: true,

File diff suppressed because one or more lines are too long

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