Compare commits

..

104 Commits

Author SHA1 Message Date
刘祥超
0d02e3f15a 优化WAF创建规则界面中参数选项显示 2023-01-13 18:51:14 +08:00
刘祥超
bf82f22d0f WAF规则中如果对比值为空,则显示空字样 2023-01-13 15:58:35 +08:00
刘祥超
e18f182ce6 版本号修改为0.6.3 2023-01-11 15:45:03 +08:00
刘祥超
761c26b587 Update Dockerfile 2023-01-10 21:32:20 +08:00
刘祥超
5e62769dcf 修改版本为0.6.2 2023-01-10 21:17:20 +08:00
刘祥超
86b8a718a0 在Dockerfile中增加local repository提示 2023-01-10 18:31:28 +08:00
刘祥超
a729cfc31d 更新Dockerfile文件 2023-01-10 10:53:48 +08:00
刘祥超
96cfda852a Dockerfile版本号更新到0.6.0 2023-01-09 20:45:04 +08:00
刘祥超
0423d9246c 更新components.js 2023-01-09 18:12:45 +08:00
刘祥超
985798757f 修复<values-box>组件内容中如果出现数字的显示问题 2023-01-09 18:08:24 +08:00
刘祥超
72876f6749 版本修改为0.6.1 2023-01-09 16:06:13 +08:00
刘祥超
03d6e223d8 更新components.js 2023-01-09 09:31:19 +08:00
刘祥超
62d9f2ed97 优化文字提示 2023-01-08 19:38:50 +08:00
刘祥超
a550a44a52 集群服务设置增加自动读超时选项 2023-01-07 20:04:42 +08:00
刘祥超
b19d586949 更新components.js 2023-01-07 19:34:48 +08:00
刘祥超
bbfa3ee57f 优化任务Badge检查频率 2023-01-07 19:34:40 +08:00
刘祥超
af409dd3b8 优化WAF规则显示样式 2023-01-06 20:06:30 +08:00
刘祥超
3db79ca149 更新components.js 2023-01-06 19:17:31 +08:00
刘祥超
e880420494 WAF规则使用中文显示运算符 2023-01-06 19:12:53 +08:00
刘祥超
28ec17b8fe 优化请求条件操作符描述 2023-01-06 16:05:06 +08:00
刘祥超
8026a40807 修改一处错别字 2023-01-05 10:27:59 +08:00
刘祥超
068c6d406a useragent改为userAgent 2023-01-02 18:20:42 +08:00
刘祥超
57470e4ef0 集群服务设置中增加性能设置 2023-01-01 19:26:59 +08:00
刘祥超
ca8e1537f5 修复文字错误 2023-01-01 18:33:05 +08:00
刘祥超
d67b818398 华为云可以设置终端节点(endpoint) 2023-01-01 18:28:37 +08:00
刘祥超
f5f46424bb 集群/节点阈值切换监控项时同时切换参数描述 2022-12-31 18:33:42 +08:00
刘祥超
1e259717ce 优化代码 2022-12-31 17:31:17 +08:00
刘祥超
91ece99a9c 优化证书加载速度 2022-12-31 17:21:48 +08:00
刘祥超
d30ebdb369 优化证书数量很多时的页面加载速度 2022-12-31 17:12:49 +08:00
刘祥超
ade8522b69 实现UA名单功能 2022-12-30 20:48:38 +08:00
刘祥超
159b308f31 内容压缩增加默认内容长度限制 2022-12-30 14:37:50 +08:00
刘祥超
837bf25f7b 内容压缩支持例外扩展名 2022-12-30 12:04:17 +08:00
刘祥超
8301d3669b 默认情况下内容压缩不支持Partial Content 2022-12-30 11:43:47 +08:00
刘祥超
cc752e8d80 优化文字提示 2022-12-29 17:19:53 +08:00
刘祥超
d20e6bd42f 增加CORS自适应跨域 2022-12-29 17:16:07 +08:00
刘祥超
bfee9fe233 优化文字提示 2022-12-27 18:55:18 +08:00
刘祥超
9c962b09f1 调整文字提示 2022-12-22 11:43:31 +08:00
刘祥超
46edefead7 服务没有设置所属用户时可以设置一个用户 2022-12-21 16:13:39 +08:00
刘祥超
725cfc8a2b 优化代码 2022-12-15 16:18:10 +08:00
刘祥超
1ce48b9ef4 安全设置中检查搜索引擎和爬虫时不区分大小写 2022-12-13 18:22:11 +08:00
刘祥超
37cc28f225 优化界面 2022-12-13 18:21:31 +08:00
刘祥超
5ebe3bb8e0 实时访问日志有弹窗打开时,暂时不更新数据 2022-12-10 19:01:57 +08:00
刘祥超
aa01512f89 优化<more-options-indicator>组件 2022-12-10 15:57:39 +08:00
刘祥超
37ff2b886a 初步完成用户电子邮箱绑定(激活) 2022-12-08 20:25:20 +08:00
刘祥超
ce18212756 自定义线路增加区域之间关系设置 2022-12-06 22:32:32 +08:00
刘祥超
08f50a274a 修复HTTPS之HSTS设置无法手动输入有效时间的Bug 2022-12-06 14:38:59 +08:00
刘祥超
892ee0013a 修复点击修改WAF规则时未保存时也会生效的Bug 2022-12-06 10:46:46 +08:00
刘祥超
e9a47041fd 刷新预热页面增加功能说明 2022-12-06 09:52:57 +08:00
刘祥超
d419fa06e8 优化文字提示 2022-12-03 14:29:30 +08:00
刘祥超
8b961a890c 优化文字提示 2022-12-01 14:40:17 +08:00
刘祥超
db32915114 更新components.js 2022-11-29 15:40:37 +08:00
刘祥超
2ffdb10cce 版本号更新为0.6.0 2022-11-29 15:40:23 +08:00
刘祥超
507fd7e5d4 更新Dockerfile中的版本号 2022-11-29 15:40:11 +08:00
刘祥超
7df599b5a9 DNS线路选择器增加代号搜索 2022-11-28 19:00:14 +08:00
刘祥超
9987334f55 版本号修改为0.5.10 2022-11-28 18:59:40 +08:00
刘祥超
d8c3365384 节点时间相差30秒钟以上才提示 2022-11-28 15:57:41 +08:00
刘祥超
2e284b5af9 版本号修改为0.5.9 2022-11-28 15:57:18 +08:00
刘祥超
89ddd4e6a3 更新依赖库 2022-11-28 11:39:38 +08:00
刘祥超
36524ea481 修复一处测试用例package引用错误 2022-11-28 11:39:08 +08:00
刘祥超
35cf693610 更新Dockerfile 2022-11-28 11:34:02 +08:00
刘祥超
42148a66bd 改进文字提示 2022-11-27 22:00:35 +08:00
刘祥超
96878715bf 优化界面 2022-11-26 19:04:55 +08:00
刘祥超
1a5f3342e7 优化文字提示 2022-11-26 15:28:01 +08:00
刘祥超
3613d13a2b 优化文字说明 2022-11-25 19:05:11 +08:00
刘祥超
7786140d85 优化代码 2022-11-24 17:20:08 +08:00
刘祥超
3a23b57f1b 优化智能DNS访问日志组件 2022-11-24 10:24:04 +08:00
刘祥超
5a6e6fba69 节点详情显示API连接状况 2022-11-22 11:27:48 +08:00
刘祥超
910b3a6162 在节点详情中显示API节点地址 2022-11-21 21:07:51 +08:00
刘祥超
0dc19bed45 节点可以单独设置所使用的API节点地址 2022-11-21 19:54:54 +08:00
刘祥超
a7bdb64301 记录名第一段允许通配符 2022-11-20 20:08:03 +08:00
刘祥超
4739072a85 Update go.mod 2022-11-18 17:37:25 +08:00
刘祥超
07bdae2488 安装时检测本地数据库时增加更多的候选密码 2022-11-18 17:07:12 +08:00
刘祥超
b84035d821 优化代码 2022-11-18 17:02:38 +08:00
刘祥超
dc0a7b9dae 优化文字提示 2022-11-18 15:45:13 +08:00
刘祥超
2937bd8de0 提交components.js 2022-11-17 17:34:21 +08:00
刘祥超
37315ef4d9 优化代码 2022-11-17 10:41:56 +08:00
刘祥超
1986fece07 优化代码 2022-11-17 10:40:57 +08:00
刘祥超
16b1657f35 优化WAF规则相关界面 2022-11-16 15:43:08 +08:00
刘祥超
c115c62cd9 去掉服务CNAME末尾的点符号,防止误解 2022-11-16 14:44:07 +08:00
刘祥超
d2df7f8d5b 节点列表页带宽使用bps显示 2022-11-16 12:02:56 +08:00
刘祥超
6cb79864e6 边缘节点支持设置多个缓存目录 2022-11-15 20:35:45 +08:00
刘祥超
982d28c7b4 docker build增加--no-cache参数 2022-11-14 15:01:13 +08:00
刘祥超
0f57516fdc Dockerfile: 使用alpine:latest代替alpine:edge 2022-11-14 14:49:19 +08:00
刘祥超
75a89defcb 构建Dockerfile时从官网下载最新版本安装包 2022-11-14 10:01:22 +08:00
刘祥超
22a6c52060 增加Docker镜像构建脚本 2022-11-13 18:46:30 +08:00
刘祥超
37607e4a41 安装过程显示更详细内容 2022-11-11 21:47:36 +08:00
刘祥超
5936155998 优化文字提示 2022-11-11 17:46:10 +08:00
刘祥超
8d76de935f 优化文字提示 2022-11-10 15:07:15 +08:00
刘祥超
9baa530064 优化文字提示 2022-11-09 18:20:18 +08:00
刘祥超
103414b338 优化<checkbox>组件 2022-11-09 17:50:22 +08:00
刘祥超
72fe68ebfe 访问日志搜索method:XXX和requestMethod:XXX方法 2022-11-09 11:58:39 +08:00
刘祥超
cfed31958b 优化文字提示 2022-11-08 09:22:09 +08:00
刘祥超
3d5fca2d36 调整默认压缩的mime types 2022-11-08 09:18:13 +08:00
刘祥超
a5710286ec 优化代码 2022-11-06 20:34:19 +08:00
刘祥超
d0ce0c6c58 优化界面 2022-11-06 15:16:21 +08:00
刘祥超
779e2cf0f2 域名跳转增加忽略跳转前端口选项 2022-11-04 20:59:20 +08:00
刘祥超
2108474777 删除不必要的文件 2022-11-04 15:19:30 +08:00
刘祥超
e25e0f1747 删除不必要的文件 2022-11-04 15:01:36 +08:00
刘祥超
e8e74b639c 优化datepicker组件 2022-11-04 14:35:32 +08:00
刘祥超
a14fcd1e50 添加和修改API节点时,检查HTTP API端口是否和GRPC端口冲突 2022-11-04 12:04:07 +08:00
刘祥超
485c0e0891 修复系统用户和平台用户access key无法禁用和删除的问题 2022-11-04 11:06:45 +08:00
刘祥超
00a19e9d43 时钟同步增加是否检查chrony选项 2022-11-03 14:58:32 +08:00
刘祥超
67d0dc0783 版本修改为0.5.8 2022-11-02 15:10:47 +08:00
刘祥超
3718c35842 提交components.js 2022-11-02 15:10:39 +08:00
178 changed files with 3087 additions and 917 deletions

1
docker/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.zip

37
docker/Dockerfile Normal file
View File

@@ -0,0 +1,37 @@
FROM alpine:latest
LABEL maintainer="iwind.liu@gmail.com"
ENV TZ "Asia/Shanghai"
ENV VERSION 0.6.2
ENV ROOT_DIR /usr/local/goedge
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip
ENV TAR_URL "https://dl.goedge.cn/edge/v${VERSION}/edge-admin-linux-amd64-plus-v${VERSION}.zip"
#ENV TAR_URL "http://192.168.2.60:8080/edge-admin-linux-amd64-plus-v${VERSION}.zip" # your local repository
RUN apk add --no-cache tzdata
RUN apk add wget
RUN mkdir ${ROOT_DIR}; \
cd ${ROOT_DIR}; \
wget ${TAR_URL} -O ${TAR_FILE}; \
apk add unzip; \
unzip ${TAR_FILE}; \
rm -f ${TAR_FILE}
RUN apk add mysql mysql-client; \
sed -e "s/\[mysqld\]/\[mysqld\]\n\ndatadir=\/var\/lib\/mysql\nport=3306\ninnodb_flush_log_at_trx_commit=2\nmax_connections=256\nmax_prepared_stmt_count=65535\nbinlog_cache_size=1M\nbinlog_stmt_cache_size=1M\nthread_cache_size=32\nbinlog_expire_logs_seconds=1209600\n\n/" /etc/my.cnf > /tmp/my.cnf; \
cp /tmp/my.cnf /etc/my.cnf; \
sed -e "s/skip-networking/#skip-networking/" /etc/my.cnf.d/mariadb-server.cnf > /tmp/mariadb-server.cnf; \
cp /tmp/mariadb-server.cnf /etc/my.cnf.d/mariadb-server.cnf; \
mysql_install_db --user=mysql
RUN mysqld_safe --user=mysql & \
sleep 5; \
mysql -uroot -hlocalhost --execute="ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';"
RUN echo -e "#!/usr/bin/env sh\n\nmysqld_safe --user=mysql &\n/usr/local/goedge/edge-admin/bin/edge-admin\n" > ${ROOT_DIR}/run.sh; \
chmod u+x ${ROOT_DIR}/run.sh
EXPOSE 7788
EXPOSE 8001
EXPOSE 3306
ENTRYPOINT [ "/usr/local/goedge/run.sh" ]

5
docker/build.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
VERSION=latest
docker build --no-cache -t goedge/edge-admin:${VERSION} .

5
docker/run.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
VERSION=latest
docker run -d -p 7788:7788 -p 8001:8001 -p 3306:3306 --name edge-admin goedge/edge-admin:${VERSION}

8
go.mod
View File

@@ -15,9 +15,9 @@ require (
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/tealeg/xlsx/v3 v3.2.3
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
golang.org/x/sys v0.2.0
google.golang.org/grpc v1.45.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gopkg.in/yaml.v3 v3.0.1
)
require (
@@ -36,8 +36,8 @@ require (
github.com/rogpeppe/fastuuid v1.2.0 // indirect
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect

20
go.sum
View File

@@ -74,6 +74,7 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
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-20220304043459-0dd944a5b475 h1:EseyfFaQOjWanGiby9KMw7PjDBMg/95tLDgIw/ns0Cw=
github.com/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
@@ -170,8 +171,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.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
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=
@@ -198,20 +199,16 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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=
@@ -266,7 +263,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -1,96 +0,0 @@
package configloaders
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/logs"
"reflect"
)
var sharedUserUIConfig *systemconfigs.UserUIConfig = nil
const (
UserUISettingName = "userUIConfig"
)
func LoadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
locker.Lock()
defer locker.Unlock()
config, err := loadUserUIConfig()
if err != nil {
return nil, err
}
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.UserUIConfig)
return &v, nil
}
func UpdateUserUIConfig(uiConfig *systemconfigs.UserUIConfig) error {
locker.Lock()
defer locker.Unlock()
var rpcClient, err = rpc.SharedRPC()
if err != nil {
return err
}
valueJSON, err := json.Marshal(uiConfig)
if err != nil {
return err
}
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
Code: UserUISettingName,
ValueJSON: valueJSON,
})
if err != nil {
return err
}
sharedUserUIConfig = uiConfig
return nil
}
func loadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
if sharedUserUIConfig != nil {
return sharedUserUIConfig, nil
}
var rpcClient, err = rpc.SharedRPC()
if err != nil {
return nil, err
}
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
Code: UserUISettingName,
})
if err != nil {
return nil, err
}
if len(resp.ValueJSON) == 0 {
sharedUserUIConfig = defaultUserUIConfig()
return sharedUserUIConfig, nil
}
config := &systemconfigs.UserUIConfig{}
err = json.Unmarshal(resp.ValueJSON, config)
if err != nil {
logs.Println("[UI_MANAGER]" + err.Error())
sharedUserUIConfig = defaultUserUIConfig()
return sharedUserUIConfig, nil
}
sharedUserUIConfig = config
return sharedUserUIConfig, nil
}
func defaultUserUIConfig() *systemconfigs.UserUIConfig {
return &systemconfigs.UserUIConfig{
ProductName: "GoEdge",
UserSystemName: "GoEdge用户系统",
ShowOpenSourceInfo: true,
ShowVersion: true,
ShowFinance: true,
BandwidthUnit: systemconfigs.BandwidthUnitBit,
ShowBandwidthCharts: true,
ShowTrafficCharts: true,
}
}

View File

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

View File

@@ -9,6 +9,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/conds/condutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
@@ -115,6 +116,16 @@ func generateComponentsJSFile() error {
buffer.Write([]byte{'\n', '\n'})
}
// WAF操作符
wafOperatorsJSON, err := json.Marshal(firewallconfigs.AllRuleOperators)
if err != nil {
logs.Println("ComponentsAction marshal waf rule operators failed: " + err.Error())
} else {
buffer.WriteString("window.WAF_RULE_OPERATORS = ")
buffer.Write(wafOperatorsJSON)
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)
if err != nil {
return err

View File

@@ -571,41 +571,30 @@ func (this *RPCClient) pickConn() *grpc.ClientConn {
defer this.locker.Unlock()
// 检查连接状态
if len(this.conns) > 0 {
var availableConns = []*grpc.ClientConn{}
for _, state := range []connectivity.State{connectivity.Ready, connectivity.Idle, connectivity.Connecting} {
var countConns = len(this.conns)
if countConns > 0 {
if countConns == 1 {
return this.conns[0]
}
for _, state := range []connectivity.State{
connectivity.Ready,
connectivity.Idle,
connectivity.Connecting,
connectivity.TransientFailure,
} {
var availableConns = []*grpc.ClientConn{}
for _, conn := range this.conns {
if conn.GetState() == state {
availableConns = append(availableConns, conn)
}
}
if len(availableConns) > 0 {
break
return this.randConn(availableConns)
}
}
if len(availableConns) > 0 {
return availableConns[rands.Int(0, len(availableConns)-1)]
}
// 关闭
for _, conn := range this.conns {
_ = conn.Close()
}
}
// 重新初始化
err := this.init()
if err != nil {
// 错误提示已经在构造对象时打印过,所以这里不再重复打印
return nil
}
if len(this.conns) == 0 {
return nil
}
return this.conns[rands.Int(0, len(this.conns)-1)]
return this.randConn(this.conns)
}
// Close 关闭
@@ -639,3 +628,14 @@ func (this *RPCClient) localIPAddrs() []string {
}
return localIPAddrs
}
func (this *RPCClient) randConn(conns []*grpc.ClientConn) *grpc.ClientConn {
var l = len(conns)
if l == 0 {
return nil
}
if l == 1 {
return conns[0]
}
return conns[rands.Int(0, l-1)]
}

View File

@@ -3,7 +3,7 @@
package utils_test
import (
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/iwind/TeaGo/assert"
"testing"
)

55
internal/utils/time.go Normal file
View File

@@ -0,0 +1,55 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
import (
"errors"
"fmt"
"github.com/iwind/TeaGo/types"
"regexp"
)
// RangeTimes 计算时间点
func RangeTimes(timeFrom string, timeTo string, everyMinutes int32) (result []string, err error) {
if everyMinutes <= 0 {
return nil, errors.New("invalid 'everyMinutes'")
}
var reg = regexp.MustCompile(`^\d{4}$`)
if !reg.MatchString(timeFrom) {
return nil, errors.New("invalid timeFrom '" + timeFrom + "'")
}
if !reg.MatchString(timeTo) {
return nil, errors.New("invalid timeTo '" + timeTo + "'")
}
if timeFrom > timeTo {
// swap
timeFrom, timeTo = timeTo, timeFrom
}
var everyMinutesInt = int(everyMinutes)
var fromHour = types.Int(timeFrom[:2])
var fromMinute = types.Int(timeFrom[2:])
var toHour = types.Int(timeTo[:2])
var toMinute = types.Int(timeTo[2:])
if fromMinute%everyMinutesInt == 0 {
result = append(result, timeFrom)
}
for {
fromMinute += everyMinutesInt
if fromMinute > 59 {
fromHour += fromMinute / 60
fromMinute = fromMinute % 60
}
if fromHour > toHour || (fromHour == toHour && fromMinute > toMinute) {
break
}
result = append(result, fmt.Sprintf("%02d%02d", fromHour, fromMinute))
}
return
}

View File

@@ -49,7 +49,11 @@ func (this *ParentAction) ErrorText(err string) {
}
func (this *ParentAction) NotFound(name string, itemId int64) {
this.ErrorPage(errors.New(name + " id: '" + strconv.FormatInt(itemId, 10) + "' is not found"))
if itemId > 0 {
this.ErrorPage(errors.New(name + " id: '" + strconv.FormatInt(itemId, 10) + "' is not found"))
} else {
this.ErrorPage(errors.New(name + " is not found"))
}
}
func (this *ParentAction) NewPage(total int64, size ...int64) *Page {
@@ -126,11 +130,8 @@ func (this *ParentAction) RPC() *rpc.RPCClient {
}
// AdminContext 获取Context
// 每个请求的context都必须是一个新的实例
func (this *ParentAction) AdminContext() context.Context {
if this.ctx != nil {
return this.ctx
}
if this.rpcClient == nil {
rpcClient, err := rpc.SharedRPC()
if err != nil {

View File

@@ -22,7 +22,7 @@ func init() {
Post("/options", new(OptionsAction)).
// AccessKeys
Prefix("/admins/accessKeys").
Prefix("/admins/accesskeys").
Get("", new(accesskeys.IndexAction)).
GetPost("/createPopup", new(accesskeys.CreatePopupAction)).
Post("/delete", new(accesskeys.DeleteAction)).

View File

@@ -4,10 +4,12 @@ import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
)
type CreatePopupAction struct {
@@ -40,11 +42,11 @@ func (this *CreatePopupAction) RunPost(params struct {
Field("name", params.Name).
Require("请输入API节点名称")
httpConfig := &serverconfigs.HTTPProtocolConfig{}
httpsConfig := &serverconfigs.HTTPSProtocolConfig{}
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
// 监听地址
listens := []*serverconfigs.NetworkAddressConfig{}
var listens = []*serverconfigs.NetworkAddressConfig{}
err := json.Unmarshal(params.ListensJSON, &listens)
if err != nil {
this.ErrorPage(err)
@@ -64,15 +66,19 @@ func (this *CreatePopupAction) RunPost(params struct {
}
// Rest监听地址
restHTTPConfig := &serverconfigs.HTTPProtocolConfig{}
restHTTPSConfig := &serverconfigs.HTTPSProtocolConfig{}
var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{}
if params.RestIsOn {
restListens := []*serverconfigs.NetworkAddressConfig{}
var restListens = []*serverconfigs.NetworkAddressConfig{}
err = json.Unmarshal(params.RestListensJSON, &restListens)
if err != nil {
this.ErrorPage(err)
return
}
if len(restListens) == 0 {
this.Fail("请至少添加一个HTTP API监听端口")
return
}
for _, addr := range restListens {
if addr.Protocol.IsHTTPFamily() {
restHTTPConfig.IsOn = true
@@ -82,10 +88,35 @@ func (this *CreatePopupAction) RunPost(params struct {
restHTTPSConfig.Listen = append(restHTTPSConfig.Listen, addr)
}
}
// 是否有端口冲突
var rpcAddresses = []string{}
for _, listen := range listens {
err := listen.Init()
if err != nil {
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
return
}
rpcAddresses = append(rpcAddresses, listen.Addresses()...)
}
for _, listen := range restListens {
err := listen.Init()
if err != nil {
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
return
}
for _, address := range listen.Addresses() {
if lists.ContainsString(rpcAddresses, address) {
this.Fail("HTTP API地址 '" + address + "' 和 GRPC地址冲突请修改后提交")
return
}
}
}
}
// 证书
certIds := []int64{}
var certIds = []int64{}
if len(params.CertIdsJSON) > 0 {
err = json.Unmarshal(params.CertIdsJSON, &certIds)
if err != nil {
@@ -97,7 +128,7 @@ func (this *CreatePopupAction) RunPost(params struct {
this.Fail("请添加至少一个证书")
}
certRefs := []*sslconfigs.SSLCertRef{}
var certRefs = []*sslconfigs.SSLCertRef{}
for _, certId := range certIds {
certRefs = append(certRefs, &sslconfigs.SSLCertRef{
IsOn: true,
@@ -131,7 +162,7 @@ func (this *CreatePopupAction) RunPost(params struct {
}
// 访问地址
accessAddrs := []*serverconfigs.NetworkAddressConfig{}
var accessAddrs = []*serverconfigs.NetworkAddressConfig{}
err = json.Unmarshal(params.AccessAddrsJSON, &accessAddrs)
if err != nil {
this.ErrorPage(err)

View File

@@ -4,10 +4,12 @@ import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
)
@@ -175,12 +177,16 @@ func (this *UpdateAction) RunPost(params struct {
var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{}
if params.RestIsOn {
restListens := []*serverconfigs.NetworkAddressConfig{}
var restListens = []*serverconfigs.NetworkAddressConfig{}
err = json.Unmarshal(params.RestListensJSON, &restListens)
if err != nil {
this.ErrorPage(err)
return
}
if len(restListens) == 0 {
this.Fail("请至少添加一个HTTP API监听端口")
return
}
for _, addr := range restListens {
if addr.Protocol.IsHTTPFamily() {
restHTTPConfig.IsOn = true
@@ -190,6 +196,31 @@ func (this *UpdateAction) RunPost(params struct {
restHTTPSConfig.Listen = append(restHTTPSConfig.Listen, addr)
}
}
// 是否有端口冲突
var rpcAddresses = []string{}
for _, listen := range listens {
err := listen.Init()
if err != nil {
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
return
}
rpcAddresses = append(rpcAddresses, listen.Addresses()...)
}
for _, listen := range restListens {
err := listen.Init()
if err != nil {
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
return
}
for _, address := range listen.Addresses() {
if lists.ContainsString(rpcAddresses, address) {
this.Fail("HTTP API地址 '" + address + "' 和 GRPC地址冲突请修改后提交")
return
}
}
}
}
// 证书

View File

@@ -10,6 +10,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/ipAddresses/ipaddressutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
@@ -300,6 +301,22 @@ func (this *DetailAction) RunGet(params struct {
lnAddrs = []string{}
}
// API节点地址
var apiNodeAddrStrings = []string{}
var apiNodeAddrs = []*serverconfigs.NetworkAddressConfig{}
if len(node.ApiNodeAddrsJSON) > 0 {
err = json.Unmarshal(node.ApiNodeAddrsJSON, &apiNodeAddrs)
if err != nil {
this.ErrorPage(err)
return
}
for _, addr := range apiNodeAddrs {
if addr.Init() == nil {
apiNodeAddrStrings = append(apiNodeAddrStrings, addr.FullAddresses()...)
}
}
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
@@ -319,6 +336,7 @@ func (this *DetailAction) RunGet(params struct {
"levelInfo": nodeconfigs.FindNodeLevel(int(node.Level)),
"lnAddrs": lnAddrs,
"enableIPLists": node.EnableIPLists,
"apiNodeAddrs": apiNodeAddrStrings,
"status": maps.Map{
"isActive": status.IsActive,
@@ -338,6 +356,8 @@ func (this *DetailAction) RunGet(params struct {
"cacheTotalDiskSize": numberutils.FormatBytes(status.CacheTotalDiskSize),
"cacheTotalMemorySize": numberutils.FormatBytes(status.CacheTotalMemorySize),
"exePath": status.ExePath,
"apiSuccessPercent": status.APISuccessPercent,
"apiAvgCostSeconds": status.APIAvgCostSeconds,
},
"group": groupMap,

View File

@@ -7,6 +7,7 @@ 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/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
@@ -57,9 +58,19 @@ func (this *IndexAction) RunGet(params struct {
}
}
var diskSubDirs = []*serverconfigs.CacheDir{}
if len(node.CacheDiskSubDirsJSON) > 0 {
err = json.Unmarshal(node.CacheDiskSubDirsJSON, &diskSubDirs)
if err != nil {
this.ErrorPage(err)
return
}
}
var nodeMap = this.Data["node"].(maps.Map)
nodeMap["maxCacheDiskCapacity"] = maxCacheDiskCapacity
nodeMap["cacheDiskDir"] = node.CacheDiskDir
nodeMap["cacheDiskSubDirs"] = diskSubDirs
nodeMap["maxCacheMemoryCapacity"] = maxCacheMemoryCapacity
this.Show()
@@ -69,6 +80,7 @@ func (this *IndexAction) RunPost(params struct {
NodeId int64
MaxCacheDiskCapacityJSON []byte
CacheDiskDir string
CacheDiskSubDirsJSON []byte
MaxCacheMemoryCapacityJSON []byte
Must *actions.Must
@@ -105,10 +117,20 @@ func (this *IndexAction) RunPost(params struct {
}
}
if len(params.CacheDiskSubDirsJSON) > 0 {
var cacheSubDirs = []*serverconfigs.CacheDir{}
err := json.Unmarshal(params.CacheDiskSubDirsJSON, &cacheSubDirs)
if err != nil {
this.ErrorPage(err)
return
}
}
_, err := this.RPC().NodeRPC().UpdateNodeCache(this.AdminContext(), &pb.UpdateNodeCacheRequest{
NodeId: params.NodeId,
MaxCacheDiskCapacity: pbMaxCacheDiskCapacity,
CacheDiskDir: params.CacheDiskDir,
CacheDiskSubDirsJSON: params.CacheDiskSubDirsJSON,
MaxCacheMemoryCapacity: pbMaxCacheMemoryCapacity,
})
if err != nil {

View File

@@ -8,6 +8,7 @@ import (
"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/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
@@ -50,6 +51,22 @@ func (this *IndexAction) RunGet(params struct {
}
this.Data["dnsResolverConfig"] = dnsResolverConfig
// API相关
apiConfigResp, err := this.RPC().NodeRPC().FindNodeAPIConfig(this.AdminContext(), &pb.FindNodeAPIConfigRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var apiNodeAddrs = []*serverconfigs.NetworkAddressConfig{}
if len(apiConfigResp.ApiNodeAddrsJSON) > 0 {
err = json.Unmarshal(apiConfigResp.ApiNodeAddrsJSON, &apiNodeAddrs)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["apiNodeAddrs"] = apiNodeAddrs
this.Show()
}
@@ -59,6 +76,8 @@ func (this *IndexAction) RunPost(params struct {
DnsResolverJSON []byte
ApiNodeAddrsJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
@@ -68,6 +87,7 @@ func (this *IndexAction) RunPost(params struct {
this.Fail("CPU线程数不能小于0")
}
// 系统设置
_, err := this.RPC().NodeRPC().UpdateNodeSystem(this.AdminContext(), &pb.UpdateNodeSystemRequest{
NodeId: params.NodeId,
MaxCPU: params.MaxCPU,
@@ -77,6 +97,7 @@ func (this *IndexAction) RunPost(params struct {
return
}
// DNS解析设置
var dnsResolverConfig = nodeconfigs.DefaultDNSResolverConfig()
err = json.Unmarshal(params.DnsResolverJSON, dnsResolverConfig)
if err != nil {
@@ -98,5 +119,28 @@ func (this *IndexAction) RunPost(params struct {
return
}
// API节点设置
var apiNodeAddrs = []*serverconfigs.NetworkAddressConfig{}
if len(params.ApiNodeAddrsJSON) > 0 {
err = json.Unmarshal(params.ApiNodeAddrsJSON, &apiNodeAddrs)
if err != nil {
this.Fail("API节点地址校验错误" + err.Error())
}
for _, addr := range apiNodeAddrs {
err = addr.Init()
if err != nil {
this.Fail("API节点地址校验错误" + err.Error())
}
}
}
_, err = this.RPC().NodeRPC().UpdateNodeAPIConfig(this.AdminContext(), &pb.UpdateNodeAPIConfigRequest{
NodeId: params.NodeId,
ApiNodeAddrsJSON: params.ApiNodeAddrsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -85,6 +85,10 @@ func (this *IndexAction) RunPost(params struct {
LogRecordServerError bool
PerformanceAutoReadTimeout bool
PerformanceAutoWriteTimeout bool
PerformanceDebug bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
@@ -124,17 +128,25 @@ func (this *IndexAction) RunPost(params struct {
}
}
// 域名
config.HTTPAll.AllowMismatchDomains = allowMismatchDomains
config.HTTPAll.AllowNodeIP = params.HttpAllAllowNodeIP
config.HTTPAll.DefaultDomain = params.HttpAllDefaultDomain
// 访问日志
config.HTTPAccessLog.EnableRequestHeaders = params.HttpAccessLogEnableRequestHeaders
config.HTTPAccessLog.EnableResponseHeaders = params.HttpAccessLogEnableResponseHeaders
config.HTTPAccessLog.CommonRequestHeadersOnly = params.HttpAccessLogCommonRequestHeadersOnly
config.HTTPAccessLog.EnableCookies = params.HttpAccessLogEnableCookies
// 日志
config.Log.RecordServerError = params.LogRecordServerError
// 性能
config.Performance.AutoReadTimeout = params.PerformanceAutoReadTimeout
config.Performance.AutoWriteTimeout = params.PerformanceAutoWriteTimeout
config.Performance.Debug = params.PerformanceDebug
err = config.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())

View File

@@ -89,6 +89,19 @@ func (this *IndexAction) RunGet(params struct {
}
}
// DNS信息
var fullDomainName = ""
if len(cluster.DnsName) > 0 && cluster.DnsDomainId > 0 {
domainResp, err := this.RPC().DNSDomainRPC().FindBasicDNSDomain(this.AdminContext(), &pb.FindBasicDNSDomainRequest{DnsDomainId: cluster.DnsDomainId})
if err != nil {
this.ErrorPage(err)
return
}
if domainResp.DnsDomain != nil {
fullDomainName = cluster.DnsName + "." + domainResp.DnsDomain.Name
}
}
this.Data["cluster"] = maps.Map{
"id": cluster.Id,
"name": cluster.Name,
@@ -100,6 +113,7 @@ func (this *IndexAction) RunGet(params struct {
"autoRemoteStart": cluster.AutoRemoteStart,
"autoInstallNftables": cluster.AutoInstallNftables,
"sshParams": sshParams,
"domainName": fullDomainName,
}
// 默认值
@@ -122,6 +136,7 @@ func (this *IndexAction) RunPost(params struct {
AutoOpenPorts bool
ClockAutoSync bool
ClockServer string
ClockCheckChrony bool
AutoRemoteStart bool
AutoInstallNftables bool
@@ -154,6 +169,7 @@ func (this *IndexAction) RunPost(params struct {
var clockConfig = nodeconfigs.DefaultClockConfig()
clockConfig.AutoSync = params.ClockAutoSync
clockConfig.Server = params.ClockServer
clockConfig.CheckChrony = params.ClockCheckChrony
clockConfigJSON, err := json.Marshal(clockConfig)
if err != nil {
this.ErrorPage(err)

View File

@@ -60,7 +60,7 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
var tabbar = actionutils.NewTabbar()
tabbar.Add("集群列表", "", "/clusters", "", false)
if teaconst.IsPlus {
tabbar.Add("集群看板", "", "/clusters/cluster/boards?clusterId="+clusterIdString, "board", selectedTabbar == "board")
tabbar.Add("集群看板", "", "/clusters/cluster/boards?clusterId="+clusterIdString, "chart line area", selectedTabbar == "board")
}
tabbar.Add("集群节点", "", "/clusters/cluster/nodes?clusterId="+clusterIdString, "server", selectedTabbar == "node")
tabbar.Add("集群设置", "", "/clusters/cluster/settings?clusterId="+clusterIdString, "setting", selectedTabbar == "setting")

View File

@@ -3,23 +3,38 @@ package tasks
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"time"
)
type CheckAction struct {
actionutils.ParentAction
}
func (this *CheckAction) RunPost(params struct{}) {
resp, err := this.RPC().NodeTaskRPC().ExistsNodeTasks(this.AdminContext(), &pb.ExistsNodeTasksRequest{
ExcludeTypes: []string{"ipItemChanged"},
})
if err != nil {
this.ErrorPage(err)
return
}
func (this *CheckAction) RunPost(params struct {
IsDoing bool
HasError bool
IsUpdated bool
}) {
var maxTries = 10
for i := 0; i < maxTries; i++ {
resp, err := this.RPC().NodeTaskRPC().ExistsNodeTasks(this.AdminContext(), &pb.ExistsNodeTasksRequest{
ExcludeTypes: []string{"ipItemChanged"},
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["isDoing"] = resp.ExistTasks
this.Data["hasError"] = resp.ExistError
// 如果没有数据变化,继续查询
if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError {
time.Sleep(3 * time.Second)
continue
}
this.Data["isDoing"] = resp.ExistTasks
this.Data["hasError"] = resp.ExistError
break
}
this.Success()
}

View File

@@ -71,7 +71,10 @@ func ValidateRecordName(name string) bool {
}
pieces := strings.Split(name, ".")
for _, piece := range pieces {
for index, piece := range pieces {
if index == 0 && piece == "*" {
continue
}
if piece == "-" ||
strings.HasPrefix(piece, "-") ||
strings.HasSuffix(piece, "-") ||
@@ -130,6 +133,11 @@ func ValidateRecordValue(recordType dnsconfigs.RecordType, value string) (messag
message = "请输入正确的邮件服务器域名"
return
}
case dnsconfigs.RecordTypeSRV:
if len(value) == 0 {
message = "请输入主机名"
return
}
case dnsconfigs.RecordTypeTXT:
if len(value) > 512 {
message = "文本长度不能超出512字节"

View File

@@ -64,6 +64,7 @@ func (this *CreatePopupAction) RunPost(params struct {
// HuaweiDNS
ParamHuaweiAccessKeyId string
ParamHuaweiAccessKeySecret string
ParamHuaweiEndpoint string
// CloudFlare
ParamCloudFlareAPIKey string
@@ -119,6 +120,7 @@ func (this *CreatePopupAction) RunPost(params struct {
apiParams["accessKeyId"] = params.ParamHuaweiAccessKeyId
apiParams["accessKeySecret"] = params.ParamHuaweiAccessKeySecret
apiParams["endpoint"] = params.ParamHuaweiEndpoint
case "cloudFlare":
params.Must.
Field("paramCloudFlareAPIKey", params.ParamCloudFlareAPIKey).

View File

@@ -91,6 +91,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
// HuaweiDNS
ParamHuaweiAccessKeyId string
ParamHuaweiAccessKeySecret string
ParamHuaweiEndpoint string
// CloudFlare
ParamCloudFlareAPIKey string
@@ -148,6 +149,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
apiParams["accessKeyId"] = params.ParamHuaweiAccessKeyId
apiParams["accessKeySecret"] = params.ParamHuaweiAccessKeySecret
apiParams["endpoint"] = params.ParamHuaweiEndpoint
case "cloudFlare":
params.Must.
Field("paramCloudFlareAPIKey", params.ParamCloudFlareAPIKey).

View File

@@ -3,21 +3,36 @@ package tasks
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"time"
)
type CheckAction struct {
actionutils.ParentAction
}
func (this *CheckAction) RunPost(params struct{}) {
resp, err := this.RPC().DNSTaskRPC().ExistsDNSTasks(this.AdminContext(), &pb.ExistsDNSTasksRequest{})
if err != nil {
this.ErrorPage(err)
return
}
func (this *CheckAction) RunPost(params struct {
IsDoing bool
HasError bool
IsUpdated bool
}) {
var maxTries = 10
for i := 0; i < maxTries; i++ {
resp, err := this.RPC().DNSTaskRPC().ExistsDNSTasks(this.AdminContext(), &pb.ExistsDNSTasksRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["isDoing"] = resp.ExistTasks
this.Data["hasError"] = resp.ExistError
// 如果没有数据变化,继续查询
if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError {
time.Sleep(3 * time.Second)
continue
}
this.Data["isDoing"] = resp.ExistTasks
this.Data["hasError"] = resp.ExistError
break
}
this.Success()
}

View File

@@ -32,6 +32,7 @@ func (this *CreateRulePopupAction) RunGet(params struct {
"params": checkpoint.Params,
"options": checkpoint.Options,
"isComposed": checkpoint.IsComposed,
"dataType": checkpoint.DataType,
})
}
}

View File

@@ -55,10 +55,11 @@ func (this *IndexAction) RunGet(params struct {
// 服务列表
serversResp, err := this.RPC().ServerRPC().ListEnabledServersMatch(this.AdminContext(), &pb.ListEnabledServersMatchRequest{
Offset: page.Offset,
Size: page.Size,
ServerGroupId: params.GroupId,
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
ServerGroupId: params.GroupId,
Keyword: params.Keyword,
IgnoreSSLCerts: true,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -43,7 +43,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err)
return
}
headerPolicyId := createHeaderPolicyResp.HeaderPolicyId
headerPolicyId := createHeaderPolicyResp.HttpHeaderPolicyId
ref := &shared.HTTPHeaderPolicyRef{
IsPrior: false,
IsOn: true,
@@ -70,7 +70,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err)
return
}
headerPolicyId := createHeaderPolicyResp.HeaderPolicyId
headerPolicyId := createHeaderPolicyResp.HttpHeaderPolicyId
ref := &shared.HTTPHeaderPolicyRef{
IsPrior: false,
IsOn: true,

View File

@@ -99,6 +99,7 @@ func (this *IndexAction) RunGet(params struct {
TrafficOutAsc: params.TrafficOutOrder == "asc",
UserId: params.UserId,
IgnoreServerNames: true,
IgnoreSSLCerts: true,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -48,7 +48,10 @@ func (this *UpdateCNAMEPopupAction) RunPost(params struct {
this.FailField("dnsName", "CNAME中只能包含数字、英文字母")
}
serverResp, err := this.RPC().ServerRPC().FindEnabledServer(this.AdminContext(), &pb.FindEnabledServerRequest{ServerId: params.ServerId})
serverResp, err := this.RPC().ServerRPC().FindEnabledServer(this.AdminContext(), &pb.FindEnabledServerRequest{
ServerId: params.ServerId,
IgnoreSSLCerts: true,
})
if err != nil {
this.ErrorPage(err)
return

View File

@@ -40,13 +40,13 @@ func (this *CreateDeletePopupAction) RunPost(params struct {
Field("name", params.Name).
Require("名称不能为空")
policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HeaderPolicyId: params.HeaderPolicyId})
policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HttpHeaderPolicyId: params.HeaderPolicyId})
if err != nil {
this.ErrorPage(err)
return
}
policyConfig := &shared.HTTPHeaderPolicy{}
err = json.Unmarshal(policyConfigResp.HeaderPolicyJSON, policyConfig)
err = json.Unmarshal(policyConfigResp.HttpHeaderPolicyJSON, policyConfig)
if err != nil {
this.ErrorPage(err)
return
@@ -55,8 +55,8 @@ func (this *CreateDeletePopupAction) RunPost(params struct {
deleteHeaders := policyConfig.DeleteHeaders
deleteHeaders = append(deleteHeaders, params.Name)
_, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicyDeletingHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicyDeletingHeadersRequest{
HeaderPolicyId: params.HeaderPolicyId,
HeaderNames: deleteHeaders,
HttpHeaderPolicyId: params.HeaderPolicyId,
HeaderNames: deleteHeaders,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -52,13 +52,13 @@ func (this *CreateSetPopupAction) RunPost(params struct {
Field("name", params.Name).
Require("请输入Header名称")
configResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HeaderPolicyId: params.HeaderPolicyId})
configResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HttpHeaderPolicyId: params.HeaderPolicyId})
if err != nil {
this.ErrorPage(err)
return
}
policyConfig := &shared.HTTPHeaderPolicy{}
err = json.Unmarshal(configResp.HeaderPolicyJSON, policyConfig)
err = json.Unmarshal(configResp.HttpHeaderPolicyJSON, policyConfig)
if err != nil {
this.ErrorPage(err)
return
@@ -135,8 +135,8 @@ func (this *CreateSetPopupAction) RunPost(params struct {
}
_, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicySettingHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicySettingHeadersRequest{
HeaderPolicyId: params.HeaderPolicyId,
HeadersJSON: refsJSON,
HttpHeaderPolicyId: params.HeaderPolicyId,
HeadersJSON: refsJSON,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -21,14 +21,14 @@ func (this *DeleteAction) RunPost(params struct {
defer this.CreateLog(oplogs.LevelInfo, "删除请求HeaderHeaderPolicyId:%d, HeaderId:%d", params.HeaderPolicyId, params.HeaderId)
policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{
HeaderPolicyId: params.HeaderPolicyId,
HttpHeaderPolicyId: params.HeaderPolicyId,
})
if err != nil {
this.ErrorPage(err)
return
}
policyConfig := &shared.HTTPHeaderPolicy{}
err = json.Unmarshal(policyConfigResp.HeaderPolicyJSON, policyConfig)
err = json.Unmarshal(policyConfigResp.HttpHeaderPolicyJSON, policyConfig)
if err != nil {
this.ErrorPage(err)
return
@@ -48,8 +48,8 @@ func (this *DeleteAction) RunPost(params struct {
return
}
_, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicySettingHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicySettingHeadersRequest{
HeaderPolicyId: params.HeaderPolicyId,
HeadersJSON: resultJSON,
HttpHeaderPolicyId: params.HeaderPolicyId,
HeadersJSON: resultJSON,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -19,12 +19,12 @@ func (this *DeleteDeletingHeaderAction) RunPost(params struct {
// 日志
defer this.CreateLog(oplogs.LevelInfo, "删除需要删除的请求HeaderHeaderPolicyId:%d, HeaderName:%s", params.HeaderPolicyId, params.HeaderName)
policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HeaderPolicyId: params.HeaderPolicyId})
policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HttpHeaderPolicyId: params.HeaderPolicyId})
if err != nil {
this.ErrorPage(err)
return
}
policyConfigJSON := policyConfigResp.HeaderPolicyJSON
policyConfigJSON := policyConfigResp.HttpHeaderPolicyJSON
policyConfig := &shared.HTTPHeaderPolicy{}
err = json.Unmarshal(policyConfigJSON, policyConfig)
if err != nil {
@@ -40,8 +40,8 @@ func (this *DeleteDeletingHeaderAction) RunPost(params struct {
headerNames = append(headerNames, h)
}
_, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicyDeletingHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicyDeletingHeadersRequest{
HeaderPolicyId: params.HeaderPolicyId,
HeaderNames: headerNames,
HttpHeaderPolicyId: params.HeaderPolicyId,
HeaderNames: headerNames,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -47,7 +47,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err)
return
}
headerPolicyId := createHeaderPolicyResp.HeaderPolicyId
var headerPolicyId = createHeaderPolicyResp.HttpHeaderPolicyId
ref := &shared.HTTPHeaderPolicyRef{
IsPrior: false,
IsOn: true,
@@ -74,7 +74,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err)
return
}
headerPolicyId := createHeaderPolicyResp.HeaderPolicyId
headerPolicyId := createHeaderPolicyResp.HttpHeaderPolicyId
ref := &shared.HTTPHeaderPolicyRef{
IsPrior: false,
IsOn: true,

View File

@@ -19,6 +19,7 @@ func init() {
GetPost("/createDeletePopup", new(CreateDeletePopupAction)).
Post("/deleteDeletingHeader", new(DeleteDeletingHeaderAction)).
Post("/delete", new(DeleteAction)).
GetPost("/updateCORSPopup", new(UpdateCORSPopupAction)).
EndAll()
})
}

View File

@@ -0,0 +1,75 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package headers
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
)
type UpdateCORSPopupAction struct {
actionutils.ParentAction
}
func (this *UpdateCORSPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdateCORSPopupAction) RunGet(params struct {
HeaderPolicyId int64
}) {
this.Data["headerPolicyId"] = params.HeaderPolicyId
resp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HttpHeaderPolicyId: params.HeaderPolicyId})
if err != nil {
this.ErrorPage(err)
return
}
var headerPolicyJSON = resp.HttpHeaderPolicyJSON
var headerPolicy = &shared.HTTPHeaderPolicy{}
if len(headerPolicyJSON) > 0 {
err = json.Unmarshal(headerPolicyJSON, headerPolicy)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["cors"] = headerPolicy.CORS
this.Show()
}
func (this *UpdateCORSPopupAction) RunPost(params struct {
HeaderPolicyId int64
CorsJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
var config = &shared.HTTPCORSHeaderConfig{}
err := json.Unmarshal(params.CorsJSON, config)
if err != nil {
this.Fail("配置校验失败:" + err.Error())
return
}
err = config.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())
return
}
_, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicyCORS(this.AdminContext(), &pb.UpdateHTTPHeaderPolicyCORSRequest{
HttpHeaderPolicyId: params.HeaderPolicyId,
CorsJSON: params.CorsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -31,7 +31,7 @@ func (this *IndexAction) RunGet(params struct {
if !isOk {
return
}
httpsConfig := &serverconfigs.HTTPSProtocolConfig{}
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
if len(server.HttpsJSON) > 0 {
err := json.Unmarshal(server.HttpsJSON, httpsConfig)
if err != nil {
@@ -44,12 +44,15 @@ func (this *IndexAction) RunGet(params struct {
var sslPolicy *sslconfigs.SSLPolicy
if httpsConfig.SSLPolicyRef != nil && httpsConfig.SSLPolicyRef.SSLPolicyId > 0 {
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{SslPolicyId: httpsConfig.SSLPolicyRef.SSLPolicyId})
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{
SslPolicyId: httpsConfig.SSLPolicyRef.SSLPolicyId,
IgnoreData: true,
})
if err != nil {
this.ErrorPage(err)
return
}
sslPolicyConfigJSON := sslPolicyConfigResp.SslPolicyJSON
var sslPolicyConfigJSON = sslPolicyConfigResp.SslPolicyJSON
if len(sslPolicyConfigJSON) > 0 {
sslPolicy = &sslconfigs.SSLPolicy{}
err = json.Unmarshal(sslPolicyConfigJSON, sslPolicy)

View File

@@ -42,7 +42,10 @@ func (this *IndexAction) RunGet(params struct {
this.Data["clusters"] = clusterMaps
// 当前服务信息
serverResp, err := this.RPC().ServerRPC().FindEnabledServer(this.AdminContext(), &pb.FindEnabledServerRequest{ServerId: params.ServerId})
serverResp, err := this.RPC().ServerRPC().FindEnabledServer(this.AdminContext(), &pb.FindEnabledServerRequest{
ServerId: params.ServerId,
IgnoreSSLCerts: true,
})
if err != nil {
this.ErrorPage(err)
return
@@ -121,6 +124,7 @@ func (this *IndexAction) RunGet(params struct {
// RunPost 保存
func (this *IndexAction) RunPost(params struct {
ServerId int64
UserId int64
Name string
Description string
ClusterId int64
@@ -157,16 +161,28 @@ func (this *IndexAction) RunPost(params struct {
return
}
// 修改套餐
if params.UserPlanId > 0 {
_, err = this.RPC().ServerRPC().UpdateServerUserPlan(this.AdminContext(), &pb.UpdateServerUserPlanRequest{
ServerId: params.ServerId,
UserPlanId: params.UserPlanId,
// 修改用户
if params.UserId > 0 {
_, err = this.RPC().ServerRPC().UpdateServerUser(this.AdminContext(), &pb.UpdateServerUserRequest{
ServerId: params.ServerId,
UserId: params.UserId,
})
if err != nil {
this.ErrorPage(err)
return
}
} else {
// 修改套餐
if params.UserPlanId > 0 {
_, err = this.RPC().ServerRPC().UpdateServerUserPlan(this.AdminContext(), &pb.UpdateServerUserPlanRequest{
ServerId: params.ServerId,
UserPlanId: params.UserPlanId,
})
if err != nil {
this.ErrorPage(err)
return
}
}
}
this.Success()

View File

@@ -36,7 +36,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err)
return
}
headerPolicyId := createHeaderPolicyResp.HeaderPolicyId
headerPolicyId := createHeaderPolicyResp.HttpHeaderPolicyId
ref := &shared.HTTPHeaderPolicyRef{
IsPrior: false,
IsOn: true,
@@ -59,7 +59,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err)
return
}
headerPolicyId := createHeaderPolicyResp.HeaderPolicyId
headerPolicyId := createHeaderPolicyResp.HttpHeaderPolicyId
ref := &shared.HTTPHeaderPolicyRef{
IsPrior: false,
IsOn: true,

View File

@@ -109,6 +109,12 @@ func (this *LocationHelper) createMenus(serverIdString string, locationIdString
"isActive": secondMenuItem == "referer",
"isOn": locationConfig.Web != nil && locationConfig.Web.Referers != nil && locationConfig.Web.Referers.IsPrior,
})
menuItems = append(menuItems, maps.Map{
"name": "UA名单",
"url": "/servers/server/settings/locations/userAgent?serverId=" + serverIdString + "&locationId=" + locationIdString,
"isActive": secondMenuItem == "userAgent",
"isOn": locationConfig.Web != nil && locationConfig.Web.UserAgent != nil && locationConfig.Web.UserAgent.IsPrior,
})
menuItems = append(menuItems, maps.Map{
"name": "字符编码",
"url": "/servers/server/settings/locations/charset?serverId=" + serverIdString + "&locationId=" + locationIdString,

View File

@@ -1,6 +1,6 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package uam
package referers
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"

View File

@@ -1,4 +1,4 @@
package uam
package referers
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"

View File

@@ -0,0 +1,62 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package userAgent
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "index")
this.SecondMenu("userAgent")
}
func (this *IndexAction) RunGet(params struct {
LocationId int64
}) {
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithLocationId(this.AdminContext(), params.LocationId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["webId"] = webConfig.Id
var userAgentConfig = webConfig.UserAgent
if userAgentConfig == nil {
userAgentConfig = serverconfigs.NewUserAgentConfig()
}
this.Data["userAgentConfig"] = userAgentConfig
this.Show()
}
func (this *IndexAction) RunPost(params struct {
WebId int64
UserAgentJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("修改Web %d User-Agent设置", params.WebId)
_, err := this.RPC().HTTPWebRPC().UpdateHTTPWebUserAgent(this.AdminContext(), &pb.UpdateHTTPWebUserAgentRequest{
HttpWebId: params.WebId,
UserAgentJSON: params.UserAgentJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,22 @@
package userAgent
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/locationutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/serverutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
Helper(locationutils.NewLocationHelper()).
Helper(serverutils.NewServerHelper()).
Data("tinyMenuItem", "userAgent").
Prefix("/servers/server/settings/locations/userAgent").
GetPost("", new(IndexAction)).
EndAll()
})
}

View File

@@ -39,10 +39,11 @@ func (this *CreatePopupAction) RunPost(params struct {
KeepArgs bool
// 域名
DomainsAll bool
DomainsBeforeJSON []byte
DomainAfter string
DomainAfterScheme string
DomainsAll bool
DomainsBeforeJSON []byte
DomainBeforeIgnorePorts bool
DomainAfter string
DomainAfterScheme string
// 端口
PortsAll bool
@@ -128,6 +129,7 @@ func (this *CreatePopupAction) RunPost(params struct {
return
}
}
config.DomainBeforeIgnorePorts = params.DomainBeforeIgnorePorts
if len(params.DomainAfter) == 0 {
this.FailField("domainAfter", "请输入跳转后域名")
return

View File

@@ -1,6 +1,6 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package uam
package referers
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"

View File

@@ -1,4 +1,4 @@
package uam
package referers
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"

View File

@@ -0,0 +1,64 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package userAgent
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "index")
this.SecondMenu("userAgent")
}
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
this.Data["serverId"] = params.ServerId
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["webId"] = webConfig.Id
var userAgentConfig = webConfig.UserAgent
if userAgentConfig == nil {
userAgentConfig = serverconfigs.NewUserAgentConfig()
}
this.Data["userAgentConfig"] = userAgentConfig
this.Show()
}
func (this *IndexAction) RunPost(params struct {
WebId int64
UserAgentJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("修改Web %d User-Agent设置", params.WebId)
_, err := this.RPC().HTTPWebRPC().UpdateHTTPWebUserAgent(this.AdminContext(), &pb.UpdateHTTPWebUserAgentRequest{
HttpWebId: params.WebId,
UserAgentJSON: params.UserAgentJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,8 +1,8 @@
package userui
package userAgent
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/settingutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/serverutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
@@ -10,9 +10,9 @@ import (
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)).
Helper(settingutils.NewHelper("userUI")).
Prefix("/settings/user-ui").
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
Helper(serverutils.NewServerHelper()).
Prefix("/servers/server/settings/userAgent").
GetPost("", new(IndexAction)).
EndAll()
})

View File

@@ -57,7 +57,10 @@ func (this *ServerHelper) createLeftMenu(action *actions.ActionObject) {
return
}
serverResp, err := rpcClient.ServerRPC().FindEnabledServer(rpcClient.Context(action.Context.GetInt64("adminId")), &pb.FindEnabledServerRequest{ServerId: serverId})
serverResp, err := rpcClient.ServerRPC().FindEnabledServer(rpcClient.Context(action.Context.GetInt64("adminId")), &pb.FindEnabledServerRequest{
ServerId: serverId,
IgnoreSSLCerts: true,
})
if err != nil {
logs.Error(err)
return
@@ -315,6 +318,12 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
"isActive": secondMenuItem == "referer",
"isOn": serverConfig.Web != nil && serverConfig.Web.Referers != nil && serverConfig.Web.Referers.IsOn,
})
menuItems = append(menuItems, maps.Map{
"name": "UA名单",
"url": "/servers/server/settings/userAgent?serverId=" + serverIdString,
"isActive": secondMenuItem == "userAgent",
"isOn": serverConfig.Web != nil && serverConfig.Web.UserAgent != nil && serverConfig.Web.UserAgent.IsOn,
})
menuItems = append(menuItems, maps.Map{
"name": "字符编码",
"url": "/servers/server/settings/charset?serverId=" + serverIdString,

View File

@@ -8,14 +8,17 @@ import (
"strconv"
)
// 查找Server
// FindServer 查找服务信息
func FindServer(p *actionutils.ParentAction, serverId int64) (*pb.Server, *serverconfigs.ServerConfig, bool) {
serverResp, err := p.RPC().ServerRPC().FindEnabledServer(p.AdminContext(), &pb.FindEnabledServerRequest{ServerId: serverId})
serverResp, err := p.RPC().ServerRPC().FindEnabledServer(p.AdminContext(), &pb.FindEnabledServerRequest{
ServerId: serverId,
IgnoreSSLCerts: true,
})
if err != nil {
p.ErrorPage(err)
return nil, nil, false
}
server := serverResp.Server
var server = serverResp.Server
if server == nil {
p.ErrorPage(errors.New("not found server with id '" + strconv.FormatInt(serverId, 10) + "'"))
return nil, nil, false

View File

@@ -1,181 +0,0 @@
package userui
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"io"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
config, err := configloaders.LoadUserUIConfig()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["config"] = config
// 时区
this.Data["timeZoneGroups"] = nodeconfigs.FindAllTimeZoneGroups()
this.Data["timeZoneLocations"] = nodeconfigs.FindAllTimeZoneLocations()
if len(config.TimeZone) == 0 {
config.TimeZone = nodeconfigs.DefaultTimeZoneLocation
}
this.Data["timeZoneLocation"] = nodeconfigs.FindTimeZoneLocation(config.TimeZone)
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ProductName string
UserSystemName string
ShowOpenSourceInfo bool
ShowVersion bool
Version string
ShowFinance bool
FaviconFile *actions.File
LogoFile *actions.File
TimeZone string
ShowTrafficCharts bool
ShowBandwidthCharts bool
BandwidthUnit string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("productName", params.ProductName).
Require("请输入产品名称").
Field("userSystemName", params.UserSystemName).
Require("请输入管理员系统名称")
config, err := configloaders.LoadUserUIConfig()
if err != nil {
this.ErrorPage(err)
return
}
config.ProductName = params.ProductName
config.UserSystemName = params.UserSystemName
config.ShowOpenSourceInfo = params.ShowOpenSourceInfo
config.ShowVersion = params.ShowVersion
config.Version = params.Version
config.ShowFinance = params.ShowFinance
config.ShowTrafficCharts = params.ShowTrafficCharts
config.ShowBandwidthCharts = params.ShowBandwidthCharts
config.BandwidthUnit = params.BandwidthUnit
config.TimeZone = params.TimeZone
// 上传Favicon文件
if params.FaviconFile != nil {
createResp, err := this.RPC().FileRPC().CreateFile(this.AdminContext(), &pb.CreateFileRequest{
Filename: params.FaviconFile.Filename,
Size: params.FaviconFile.Size,
IsPublic: true,
})
if err != nil {
this.ErrorPage(err)
return
}
fileId := createResp.FileId
// 上传内容
buf := make([]byte, 512*1024)
reader, err := params.FaviconFile.OriginFile.Open()
if err != nil {
this.ErrorPage(err)
return
}
for {
n, err := reader.Read(buf)
if n > 0 {
_, err = this.RPC().FileChunkRPC().CreateFileChunk(this.AdminContext(), &pb.CreateFileChunkRequest{
FileId: fileId,
Data: buf[:n],
})
if err != nil {
this.Fail("上传失败:" + err.Error())
}
}
if err != nil {
if err == io.EOF {
break
}
this.Fail("上传失败:" + err.Error())
}
}
// 置为已完成
_, err = this.RPC().FileRPC().UpdateFileFinished(this.AdminContext(), &pb.UpdateFileFinishedRequest{FileId: fileId})
if err != nil {
this.ErrorPage(err)
}
config.FaviconFileId = fileId
}
// 上传Logo文件
if params.LogoFile != nil {
createResp, err := this.RPC().FileRPC().CreateFile(this.AdminContext(), &pb.CreateFileRequest{
Filename: params.LogoFile.Filename,
Size: params.LogoFile.Size,
IsPublic: true,
})
if err != nil {
this.ErrorPage(err)
return
}
fileId := createResp.FileId
// 上传内容
buf := make([]byte, 512*1024)
reader, err := params.LogoFile.OriginFile.Open()
if err != nil {
this.ErrorPage(err)
return
}
for {
n, err := reader.Read(buf)
if n > 0 {
_, err = this.RPC().FileChunkRPC().CreateFileChunk(this.AdminContext(), &pb.CreateFileChunkRequest{
FileId: fileId,
Data: buf[:n],
})
if err != nil {
this.Fail("上传失败:" + err.Error())
}
}
if err != nil {
if err == io.EOF {
break
}
this.Fail("上传失败:" + err.Error())
}
}
// 置为已完成
_, err = this.RPC().FileRPC().UpdateFileFinished(this.AdminContext(), &pb.UpdateFileFinishedRequest{FileId: fileId})
if err != nil {
this.ErrorPage(err)
}
config.LogoFileId = fileId
}
err = configloaders.UpdateUserUIConfig(config)
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
"net"
"os"
"strings"
"time"
)
@@ -31,10 +32,21 @@ func (this *DetectDBAction) RunPost(params struct{}) {
localPort = "3306"
var username = "root"
for _, pass := range []string{"", "123456", "654321", "Aa_123456"} {
var passwords = []string{"", "123456", "654321", "Aa_123456", "111111"}
// 使用 foolish-mysql 安装的MySQL
localGeneratedPasswordData, err := os.ReadFile("/usr/local/mysql/generated-password.txt")
if err == nil {
var localGeneratedPassword = strings.TrimSpace(string(localGeneratedPasswordData))
if len(localGeneratedPassword) > 0 {
passwords = append(passwords, localGeneratedPassword)
}
}
for _, pass := range passwords {
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
Driver: "mysql",
Dsn: username + ":" + pass + "@tcp(" + configutils.QuoteIP(localHost) + ":" + localPort + ")/edges11111",
Dsn: username + ":" + pass + "@tcp(" + configutils.QuoteIP(localHost) + ":" + localPort + ")/edges",
Prefix: "",
})
if err == nil {

View File

@@ -22,7 +22,7 @@ func (this *IndexAction) RunGet(params struct{}) {
currentHost = host
}
}
if net.ParseIP(currentHost) != nil {
if net.ParseIP(currentHost) != nil && currentHost != "localhost" && currentHost != "127.0.0.1" {
this.Data["currentHost"] = currentHost
} else {
this.Data["currentHost"] = ""

View File

@@ -17,13 +17,17 @@ import (
"github.com/iwind/TeaGo/maps"
"github.com/iwind/gosock/pkg/gosock"
"gopkg.in/yaml.v3"
"io"
"os"
"os/exec"
"strings"
"time"
)
type InstallAction struct {
actionutils.ParentAction
apiSetupFinished bool
}
func (this *InstallAction) RunPost(params struct {
@@ -40,7 +44,7 @@ func (this *InstallAction) RunPost(params struct {
// API节点配置
currentStatusText = "正在检查API节点配置"
apiNodeMap := maps.Map{}
var apiNodeMap = maps.Map{}
err := json.Unmarshal(params.ApiNodeJSON, &apiNodeMap)
if err != nil {
this.Fail("API节点配置数据解析错误请刷新页面后重新尝试安装错误信息" + err.Error())
@@ -48,7 +52,7 @@ func (this *InstallAction) RunPost(params struct {
// 数据库
currentStatusText = "正在检查数据库配置"
dbMap := maps.Map{}
var dbMap = maps.Map{}
err = json.Unmarshal(params.DbJSON, &dbMap)
if err != nil {
this.Fail("数据库配置数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error())
@@ -56,14 +60,14 @@ func (this *InstallAction) RunPost(params struct {
// 管理员
currentStatusText = "正在检查管理员配置"
adminMap := maps.Map{}
var adminMap = maps.Map{}
err = json.Unmarshal(params.AdminJSON, &adminMap)
if err != nil {
this.Fail("管理员数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error())
}
// 安装API节点
mode := apiNodeMap.GetString("mode")
var mode = apiNodeMap.GetString("mode")
if mode == "new" {
currentStatusText = "准备启动新API节点"
@@ -74,7 +78,7 @@ func (this *InstallAction) RunPost(params struct {
// ...
// 检查环境
apiNodeDir := Tea.Root + "/edge-api"
var apiNodeDir = Tea.Root + "/edge-api"
for _, dir := range []string{"edge-api", "edge-api/configs", "edge-api/bin"} {
apiNodeDir := Tea.Root + "/" + dir
_, err = os.Stat(apiNodeDir)
@@ -87,7 +91,7 @@ func (this *InstallAction) RunPost(params struct {
}
// 保存数据库配置
dsn := dbMap.GetString("username") + ":" + dbMap.GetString("password") + "@tcp(" + configutils.QuoteIP(dbMap.GetString("host")) + ":" + dbMap.GetString("port") + ")/" + dbMap.GetString("database") + "?charset=utf8mb4&timeout=30s"
var dsn = dbMap.GetString("username") + ":" + dbMap.GetString("password") + "@tcp(" + configutils.QuoteIP(dbMap.GetString("host")) + ":" + dbMap.GetString("port") + ")/" + dbMap.GetString("database") + "?charset=utf8mb4&timeout=30s"
dbConfig := &dbs.Config{
DBs: map[string]*dbs.DBConfig{
"prod": {
@@ -108,7 +112,7 @@ func (this *InstallAction) RunPost(params struct {
// 生成备份文件
homeDir, _ := os.UserHomeDir()
backupDirs := []string{"/etc/edge-api"}
var backupDirs = []string{"/etc/edge-api"}
if len(homeDir) > 0 {
backupDirs = append(backupDirs, homeDir+"/.edge-api")
}
@@ -151,15 +155,21 @@ func (this *InstallAction) RunPost(params struct {
var resultMap = maps.Map{}
logs.Println("[INSTALL]setup edge-api")
{
cmd := exec.Command(apiNodeDir+"/bin/edge-api", "setup", "-api-node-protocol=http", "-api-node-host=\""+apiNodeMap.GetString("newHost")+"\"", "-api-node-port=\""+apiNodeMap.GetString("newPort")+"\"")
output := bytes.NewBuffer([]byte{})
this.apiSetupFinished = false
var cmd = exec.Command(apiNodeDir+"/bin/edge-api", "setup", "-api-node-protocol=http", "-api-node-host=\""+apiNodeMap.GetString("newHost")+"\"", "-api-node-port=\""+apiNodeMap.GetString("newPort")+"\"")
var output = bytes.NewBuffer([]byte{})
cmd.Stdout = output
// 试图读取执行日志
go this.startReadingAPIInstallLog()
err = cmd.Run()
this.apiSetupFinished = true
if err != nil {
this.Fail("安装失败:" + err.Error())
}
resultData := output.Bytes()
var resultData = output.Bytes()
err = json.Unmarshal(resultData, &resultMap)
if err != nil {
this.Fail("安装节点时返回数据错误:" + err.Error() + "(" + string(resultData) + ")")
@@ -175,7 +185,7 @@ func (this *InstallAction) RunPost(params struct {
// 关闭正在运行的API节点防止冲突
logs.Println("[INSTALL]stop edge-api")
{
cmd := exec.Command(apiNodeDir+"/bin/edge-api", "stop")
var cmd = exec.Command(apiNodeDir+"/bin/edge-api", "stop")
_ = cmd.Run()
}
@@ -183,7 +193,7 @@ func (this *InstallAction) RunPost(params struct {
currentStatusText = "正在启动API节点"
logs.Println("[INSTALL]start edge-api")
{
cmd := exec.Command(apiNodeDir + "/bin/edge-api")
var cmd = exec.Command(apiNodeDir + "/bin/edge-api")
err = cmd.Start()
if err != nil {
this.Fail("API节点启动失败" + err.Error())
@@ -220,7 +230,7 @@ func (this *InstallAction) RunPost(params struct {
}
// 写入API节点配置完成安装
apiConfig := &configs.APIConfig{
var apiConfig = &configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
@@ -283,7 +293,7 @@ func (this *InstallAction) RunPost(params struct {
this.Success()
} else if mode == "old" {
// 构造RPC
apiConfig := &configs.APIConfig{
var apiConfig = &configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
@@ -303,7 +313,7 @@ func (this *InstallAction) RunPost(params struct {
}()
// 设置管理员
ctx := client.APIContext(0)
var ctx = client.APIContext(0)
_, err = client.AdminRPC().CreateOrUpdateAdmin(ctx, &pb.CreateOrUpdateAdminRequest{
Username: adminMap.GetString("username"),
Password: adminMap.GetString("password"),
@@ -344,3 +354,73 @@ func (this *InstallAction) RunPost(params struct {
this.Fail("错误的API节点模式'" + mode + "'")
}
}
// 读取API安装时的日志以便于显示当前正在执行的任务
func (this *InstallAction) startReadingAPIInstallLog() {
var tmpDir = os.TempDir()
if len(tmpDir) == 0 {
return
}
var logFile = tmpDir + "/edge-install.log"
var logFp *os.File
var err error
// 尝试5秒钟
for i := 0; i < 10; i++ {
logFp, err = os.Open(logFile)
if err != nil {
time.Sleep(1 * time.Second)
continue
} else {
break
}
}
if err != nil {
return
}
if this.apiSetupFinished {
_ = logFp.Close()
return
}
go func() {
defer func() {
_ = logFp.Close()
}()
var ticker = time.NewTicker(1 * time.Second)
var logBuf = make([]byte, 256)
for range ticker.C {
if this.apiSetupFinished {
return
}
_, err = logFp.Seek(-256, io.SeekEnd)
if err != nil {
currentStatusText = ""
return
}
n, err := logFp.Read(logBuf)
if err != nil {
currentStatusText = ""
return
}
if n > 0 {
var logData = string(logBuf[:n])
var lines = strings.Split(logData, "\n")
if len(lines) >= 3 {
var line = strings.TrimSpace(lines[len(lines)-2])
if len(line) > 0 {
if !this.apiSetupFinished {
currentStatusText = "正在执行 " + line + " ..."
}
}
}
}
}
}()
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/conds/condutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/actions"
@@ -123,6 +124,16 @@ func (this *ComponentsAction) RunGet(params struct{}) {
buffer.Write([]byte{'\n', '\n'})
}
// WAF操作符
wafOperatorsJSON, err := json.Marshal(firewallconfigs.AllRuleOperators)
if err != nil {
logs.Println("ComponentsAction marshal waf rule operators failed: " + err.Error())
} else {
buffer.WriteString("window.WAF_RULE_OPERATORS = ")
buffer.Write(wafOperatorsJSON)
buffer.Write([]byte{'\n', '\n'})
}
componentsData = buffer.Bytes()
// ETag

View File

@@ -25,7 +25,7 @@ func init() {
Get("/otpQrcode", new(OtpQrcodeAction)).
// AccessKeys
Prefix("/users/accessKeys").
Prefix("/users/accesskeys").
Get("", new(accesskeys.IndexAction)).
GetPost("/createPopup", new(accesskeys.CreatePopupAction)).
Post("/delete", new(accesskeys.DeleteAction)).

View File

@@ -97,6 +97,7 @@ func (this *UserAction) RunGet(params struct {
"username": user.Username,
"fullname": user.Fullname,
"email": user.Email,
"verifiedEmail": user.VerifiedEmail,
"tel": user.Tel,
"remark": user.Remark,
"mobile": user.Mobile,

View File

@@ -118,16 +118,16 @@ func FindAllMenuMaps(nodeLogsType string, countUnreadNodeLogs int64, countUnread
"subtitle": "集群列表",
"icon": "globe",
"subItems": []maps.Map{
{
"name": "问题修复",
"url": "/dns/issues",
"code": "issue",
},
{
"name": "DNS服务商",
"url": "/dns/providers",
"code": "provider",
},
{
"name": "问题修复",
"url": "/dns/issues",
"code": "issue",
},
},
},
{

View File

@@ -112,8 +112,8 @@ func checkIPWithoutCache(config *systemconfigs.SecurityConfig, ipAddr string) bo
}
// 请求检查相关正则
var searchEngineRegex = regexp.MustCompile(`60spider|adldxbot|adsbot-google|applebot|admantx|alexa|baidu|bingbot|bingpreview|facebookexternalhit|googlebot|proximic|slurp|sogou|twitterbot|yandex`)
var spiderRegexp = regexp.MustCompile(`python|pycurl|http-client|httpclient|apachebench|nethttp|http_request|java|perl|ruby|scrapy|php|rust|curl|wget`) // 其中增加了curl和wget
var searchEngineRegex = regexp.MustCompile(`(?i)(60spider|adldxbot|adsbot-google|applebot|admantx|alexa|baidu|bingbot|bingpreview|facebookexternalhit|googlebot|proximic|slurp|sogou|twitterbot|yandex)`)
var spiderRegexp = regexp.MustCompile(`(?i)(python|pycurl|http-client|httpclient|apachebench|nethttp|http_request|java|perl|ruby|scrapy|php|rust|curl|wget)`) // 其中增加了curl和wget
// 检查请求
func checkRequestSecurity(securityConfig *systemconfigs.SecurityConfig, req *http.Request) bool {

View File

@@ -84,6 +84,7 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/reverseProxy"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/rewrite"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/stat"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/userAgent"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/waf"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/web"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/webp"
@@ -102,6 +103,7 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/tls"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/udp"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/unix"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/userAgent"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/waf"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/web"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/webp"
@@ -124,7 +126,6 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ui"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/updates"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/upgrade"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/user-ui"
// 恢复
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/recover"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -35,7 +35,7 @@ Vue.component("checkbox", {
this.newValue = ""
},
isChecked: function () {
return this.newValue == this.elementValue
return (typeof (this.newValue) == "boolean" && this.newValue) || this.newValue == this.elementValue
}
},
watch: {

View File

@@ -38,6 +38,13 @@ Vue.component("datepicker", {
watch: {
value: function (v) {
this.day = v
let picker = this.$refs.dayInput.picker
if (picker != null) {
if (v != null && /^\d+-\d+-\d+$/.test(v)) {
picker.setDate(v)
}
}
}
},
methods: {

View File

@@ -2,6 +2,7 @@
* 更多选项
*/
Vue.component("more-options-indicator", {
props:[],
data: function () {
return {
visible: false
@@ -14,6 +15,7 @@ Vue.component("more-options-indicator", {
Tea.Vue.moreOptionsVisible = this.visible
}
this.$emit("change", this.visible)
this.$emit("input", this.visible)
}
},
template: '<a href="" style="font-weight: normal" @click.prevent="changeVisible()"><slot><span v-if="!visible">更多选项</span><span v-if="visible">收起选项</span></slot> <i class="icon angle" :class="{down:!visible, up:visible}"></i> </a>'

View File

@@ -1,5 +1,5 @@
Vue.component("values-box", {
props: ["values", "v-values", "size", "maxlength", "name", "placeholder"],
props: ["values", "v-values", "size", "maxlength", "name", "placeholder", "v-allow-empty"],
data: function () {
let values = this.values;
if (values == null) {
@@ -39,7 +39,9 @@ Vue.component("values-box", {
},
confirm: function () {
if (this.value.length == 0) {
return
if (typeof(this.vAllowEmpty) != "boolean" || !this.vAllowEmpty) {
return
}
}
if (this.isUpdating) {
@@ -75,12 +77,17 @@ Vue.component("values-box", {
},
template: `<div>
<div v-show="!isEditing && realValues.length > 0">
<div class="ui label tiny basic" v-for="(value, index) in realValues" style="margin-top:0.4em;margin-bottom:0.4em">{{value}}</div>
<div class="ui label tiny basic" v-for="(value, index) in realValues" style="margin-top:0.4em;margin-bottom:0.4em">
<span v-if="value.toString().length > 0">{{value}}</span>
<span v-if="value.toString().length == 0" class="disabled">[空]</span>
</div>
<a href="" @click.prevent="startEditing" style="font-size: 0.8em; margin-left: 0.2em">[修改]</a>
</div>
<div v-show="isEditing || realValues.length == 0">
<div style="margin-bottom: 1em" v-if="realValues.length > 0">
<div class="ui label tiny basic" v-for="(value, index) in realValues" style="margin-top:0.4em;margin-bottom:0.4em">{{value}}
<div class="ui label tiny basic" v-for="(value, index) in realValues" style="margin-top:0.4em;margin-bottom:0.4em">
<span v-if="value.toString().length > 0">{{value}}</span>
<span v-if="value.toString().length == 0" class="disabled">[空]</span>
<input type="hidden" :name="name" :value="value"/>
&nbsp; <a href="" @click.prevent="update(index)" title="修改"><i class="icon pencil small" ></i></a>
<a href="" @click.prevent="remove(index)" title="删除"><i class="icon remove"></i></a>

View File

@@ -80,7 +80,7 @@ Vue.component("dns-route-selector", {
return
}
this.searchingRoutes = this.vAllRoutes.filter(function (route) {
return teaweb.match(route.name, keyword) || teaweb.match(route.domainName, keyword)
return teaweb.match(route.name, keyword) || teaweb.match(route.code, keyword) || teaweb.match(route.domainName, keyword)
})
if (this.searchingRoutes.length > 0) {
this.routeCode = this.searchingRoutes[0].code + "@" + this.searchingRoutes[0].domainId
@@ -99,24 +99,25 @@ Vue.component("dns-route-selector", {
</div>
<button type="button" class="ui button small" @click.prevent="add" v-if="!isAdding">+</button>
<div v-if="isAdding">
<div class="ui fields inline">
<div class="ui field">
<select class="ui dropdown" style="width: 18em" v-model="routeCode">
<option value="" v-if="keyword.length == 0">[请选择]</option>
<option v-for="route in searchingRoutes" :value="route.code + '@' + route.domainId">{{route.name}}{{route.domainName}}</option>
</select>
</div>
<div class="ui field">
<input type="text" placeholder="搜索..." size="10" v-model="keyword" ref="keywordRef" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
</div>
<div class="ui field">
<button class="ui button tiny" type="button" @click.prevent="confirm">确定</button>
</div>
<div class="ui field">
<a href="" @click.prevent="cancel()"><i class="icon remove small"></i></a>
</div>
</div>
<table class="ui table">
<tr>
<td class="title">所有线路</td>
<td>
<select class="ui dropdown auto-width" v-model="routeCode">
<option value="" v-if="keyword.length == 0">[请选择]</option>
<option v-for="route in searchingRoutes" :value="route.code + '@' + route.domainId">{{route.name}}{{route.code}}/{{route.domainName}}</option>
</select>
</td>
</tr>
<tr>
<td>搜索</td>
<td>
<input type="text" placeholder="搜索..." size="10" style="width: 10em" v-model="keyword" ref="keywordRef" @keyup.enter="confirm" @keypress.enter.prevent="1"/>
</td>
</tr>
</table>
<button class="ui button tiny" type="button" @click.prevent="confirm">确定</button> &nbsp; <a href="" @click.prevent="cancel()"><i class="icon remove small"></i></a>
</div>
</div>`
})

View File

@@ -0,0 +1,79 @@
Vue.component("node-cache-disk-dirs-box", {
props: ["value", "name"],
data: function () {
let dirs = this.value
if (dirs == null) {
dirs = []
}
return {
dirs: dirs,
isEditing: false,
isAdding: false,
addingPath: ""
}
},
methods: {
add: function () {
this.isAdding = true
let that = this
setTimeout(function () {
that.$refs.addingPath.focus()
}, 100)
},
confirm: function () {
let addingPath = this.addingPath.trim()
if (addingPath.length == 0) {
let that = this
teaweb.warn("请输入要添加的缓存目录", function () {
that.$refs.addingPath.focus()
})
return
}
if (addingPath[0] != "/") {
addingPath = "/" + addingPath
}
this.dirs.push({
path: addingPath
})
this.cancel()
},
cancel: function () {
this.addingPath = ""
this.isAdding = false
this.isEditing = false
},
remove: function (index) {
let that = this
teaweb.confirm("确定要删除此目录吗?", function () {
that.dirs.$remove(index)
})
}
},
template: `<div>
<input type="hidden" :name="name" :value="JSON.stringify(dirs)"/>
<div style="margin-bottom: 0.3em">
<span class="ui label small basic" v-for="(dir, index) in dirs">
<i class="icon folder"></i>{{dir.path}} &nbsp; <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
</span>
</div>
<!-- 添加 -->
<div v-if="isAdding">
<div class="ui fields inline">
<div class="ui field">
<input type="text" style="width: 30em" v-model="addingPath" @keyup.enter="confirm()" @keypress.enter.prevent="1" @keydown.esc="cancel()" ref="addingPath" placeholder="新的缓存目录,比如 /mnt/cache"/>
</div>
<div class="ui field">
<button class="ui button small" type="button" @click.prevent="confirm">确定</button>
&nbsp; <a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
</div>
</div>
</div>
<div v-if="!isAdding">
<button class="ui button tiny" type="button" @click.prevent="add">+</button>
</div>
</div>`
})

View File

@@ -13,9 +13,9 @@ Vue.component("ns-access-log-box", {
if (accessLog.recordValue == null || accessLog.recordValue.length == 0) {
isFailure = true
}
} else if (accessLog.nsRecordId == null || accessLog.nsRecordId == 0) {
isFailure = true
}
// 没有找到记录的不需要高亮显示,防止管理员看到红色就心理恐慌
}
return {

View File

@@ -26,7 +26,8 @@ Vue.component("ns-route-ranges-box", {
// region
regions: [],
regionType: "country"
regionType: "country",
regionConnector: "OR"
}
},
methods: {
@@ -68,6 +69,7 @@ Vue.component("ns-route-ranges-box", {
this.isAdding = false
this.regions = []
this.regionType = "country"
this.regionConnector = "OR"
this.isReverse = false
},
confirmIPRange: function () {
@@ -131,6 +133,7 @@ Vue.component("ns-route-ranges-box", {
}
this.ranges.push({
type: "region",
connector: this.regionConnector,
params: {
regions: this.regions,
isReverse: this.isReverse
@@ -362,10 +365,24 @@ Vue.component("ns-route-ranges-box", {
<span class="red" v-if="range.params.isReverse">[排除]</span>
<span v-if="range.type == 'ipRange'">IP范围</span>
<span v-if="range.type == 'cidr'">CIDR</span>
<span v-if="range.type == 'region'">区域:</span>
<span v-if="range.type == 'region'"></span>
<span v-if="range.type == 'ipRange'">{{range.params.ipFrom}} - {{range.params.ipTo}}</span>
<span v-if="range.type == 'cidr'">{{range.params.cidr}}</span>
<span v-if="range.type == 'region'"><span v-for="(region, index) in range.params.regions">{{region.name}}<span v-if="index < range.params.regions.length - 1"></span></span></span>
<span v-if="range.type == 'region'">
<span v-for="(region, index) in range.params.regions">
<span v-if="region.type == 'country'">国家/地区</span>
<span v-if="region.type == 'province'">省份</span>
<span v-if="region.type == 'city'">城市</span>
<span v-if="region.type == 'provider'">ISP</span>
{{region.name}}
<span v-if="index < range.params.regions.length - 1" class="grey">
&nbsp;
<span v-if="range.connector == 'OR' || range.connector == '' || range.connector == null">或</span>
<span v-if="range.connector == 'AND'">且</span>
&nbsp;
</span>
</span>
</span>
&nbsp; <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
</div>
<div class="ui divider"></div>
@@ -488,9 +505,21 @@ Vue.component("ns-route-ranges-box", {
<tr>
<td>已添加</td>
<td>
<div v-for="(region, index) in regions" class="ui label small basic">
{{region.name}} <a href="" title="删除" @click.prevent="removeRegion(index)"><i class="icon remove small"></i></a>
</div>
<span v-for="(region, index) in regions">
<span class="ui label small basic">
<span v-if="region.type == 'country'">国家/地区</span>
<span v-if="region.type == 'province'">省份</span>
<span v-if="region.type == 'city'">城市</span>
<span v-if="region.type == 'provider'">ISP</span>
{{region.name}} <a href="" title="删除" @click.prevent="removeRegion(index)"><i class="icon remove small"></i></a>
</span>
<span v-if="index < regions.length - 1" class="grey">
&nbsp;
<span v-if="regionConnector == 'OR' || regionConnector == ''">或</span>
<span v-if="regionConnector == 'AND'">且</span>
&nbsp;
</span>
</span>
</td>
</tr>
<tr>
@@ -526,6 +555,17 @@ Vue.component("ns-route-ranges-box", {
</div>
</td>
</tr>
<tr>
<td>区域之间关系</td>
<td>
<select class="ui dropdown auto-width" v-model="regionConnector">
<option value="OR">或</option>
<option value="AND">且</option>
</select>
<p class="comment" v-if="regionConnector == 'OR'">匹配所选任一区域即认为匹配成功。</p>
<p class="comment" v-if="regionConnector == 'AND'">匹配所有所选区域才认为匹配成功。</p>
</td>
</tr>
<tr>
<td>排除</td>
<td>

View File

@@ -88,6 +88,7 @@ Vue.component("ns-routes-selector", {
<option value="isp">运营商</option>
<option value="china">中国省市</option>
<option value="world">全球国家地区</option>
<option value="agent">搜索引擎</option>
</select>
</div>

View File

@@ -0,0 +1,102 @@
Vue.component("email-sender", {
props: ["value", "name"],
data: function () {
let value = this.value
if (value == null) {
value = {
isOn: false,
smtpHost: "",
smtpPort: 0,
username: "",
password: "",
fromEmail: "",
fromName: ""
}
}
let smtpPortString = value.smtpPort.toString()
if (smtpPortString == "0") {
smtpPortString = ""
}
return {
config: value,
smtpPortString: smtpPortString
}
},
watch: {
smtpPortString: function (v) {
let port = parseInt(v)
if (!isNaN(port)) {
this.config.smtpPort = port
}
}
},
methods: {
test: function () {
window.TESTING_EMAIL_CONFIG = this.config
teaweb.popup("/users/setting/emailTest", {
height: "36em"
})
}
},
template: `<div>
<input type="hidden" :name="name" :value="JSON.stringify(config)"/>
<table class="ui table selectable definition">
<tbody>
<tr>
<td class="title">启用</td>
<td><checkbox v-model="config.isOn"></checkbox></td>
</tr>
</tbody>
<tbody v-show="config.isOn">
<tr>
<td>SMTP地址 *</td>
<td>
<input type="text" :name="name + 'SmtpHost'" v-model="config.smtpHost"/>
<p class="comment">SMTP主机地址比如<code-label>smtp.qq.com</code-label>目前仅支持TLS协议如不清楚请查询对应邮件服务商文档。</p>
</td>
</tr>
<tr>
<td>SMTP端口 *</td>
<td>
<input type="text" :name="name + 'SmtpPort'" v-model="smtpPortString" style="width: 5em" maxlength="5"/>
<p class="comment">SMTP主机端口比如<code-label>587</code-label>、<code-label>465</code-label>,如不清楚,请查询对应邮件服务商文档。</p>
</td>
</tr>
<tr>
<td>用户名 *</td>
<td>
<input type="text" :name="name + 'Username'" v-model="config.username"/>
<p class="comment">通常为发件人邮箱地址。</p>
</td>
</tr>
<tr>
<td>密码 *</td>
<td>
<input type="password" :name="name + 'Password'" v-model="config.password"/>
<p class="comment">邮箱登录密码或授权码,如不清楚,请查询对应邮件服务商文档。。</p>
</td>
</tr>
<tr>
<td>发件人Email *</td>
<td>
<input type="text" :name="name + 'FromEmail'" v-model="config.fromEmail" maxlength="128"/>
<p class="comment">使用的发件人邮箱地址,通常和发件用户名一致。</p>
</td>
</tr>
<tr>
<td>发件人名称</td>
<td>
<input type="text" :name="name + 'FromName'" v-model="config.fromName" maxlength="30"/>
<p class="comment">使用的发件人名称,默认使用系统设置的<a href="/settings/ui" target="_blank">产品名称</a>。</p>
</td>
</tr>
<tr>
<td>发送测试</td>
<td><a href="" @click.prevent="test">[点此测试]</a></td>
</tr>
</tbody>
</table>
<div class="margin"></div>
</div>`
})

View File

@@ -227,7 +227,7 @@ Vue.component("domains-box", {
&nbsp; <a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
</div>
</div>
<p class="comment" v-if="supportWildcard">支持普通域名(<code-label>example.com</code-label>)、泛域名(<code-label>*.example.com</code-label><span v-if="vSupportWildcard == undefined">、域名后缀(以点号开头,如<code-label>.example.com</code-label>)和正则表达式(以波浪号开头,如<code-label>~.*.example.com</code-label></span>。</p>
<p class="comment" v-if="supportWildcard">支持普通域名(<code-label>example.com</code-label>)、泛域名(<code-label>*.example.com</code-label><span v-if="vSupportWildcard == undefined">、域名后缀(以点号开头,如<code-label>.example.com</code-label>)和正则表达式(以波浪号开头,如<code-label>~.*.example.com</code-label></span>;如果域名后有端口,请加上端口号。</p>
<p class="comment" v-if="!supportWildcard">只支持普通域名(<code-label>example.com</code-label>、<code-label>www.example.com</code-label>)。</p>
<div class="ui divider"></div>
</div>

View File

@@ -58,7 +58,7 @@ Vue.component("http-access-log-config-box", {
<prior-checkbox :v-config="accessLog" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || accessLog.isPrior">
<tr>
<td class="title">启访问日志</td>
<td class="title">启访问日志</td>
<td>
<div class="ui checkbox">
<input type="checkbox" v-model="accessLog.isOn"/>

View File

@@ -82,7 +82,7 @@ Vue.component("http-access-log-search-box", {
<a class="ui label basic" :class="{disabled: keyword.length == 0}" @click.prevent="cleanKeyword"><i class="icon remove small"></i></a>
</div>
</div>
<div class="ui field"><tip-icon content="一些特殊的关键词:<br/>单个状态码status:200<br/>状态码范围status:500-504<br/>查询IPip:192.168.1.100<br/>查询URLhttps://goedge.cn/docs<br/>查询路径部分requestPath:/hello/world<br/>查询协议版本proto:HTTP/1.1<br/>协议scheme:http"></tip-icon></div>
<div class="ui field"><tip-icon content="一些特殊的关键词:<br/>单个状态码status:200<br/>状态码范围status:500-504<br/>查询IPip:192.168.1.100<br/>查询URLhttps://goedge.cn/docs<br/>查询路径部分requestPath:/hello/world<br/>查询协议版本proto:HTTP/1.1<br/>协议scheme:http<br/>请求方法method:POST"></tip-icon></div>
</div>
<div class="ui fields inline" style="margin-top: 0.5em">
<div class="ui field">

View File

@@ -26,7 +26,7 @@ Vue.component("http-charsets-box", {
<prior-checkbox :v-config="charsetConfig" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || charsetConfig.isPrior">
<tr>
<td class="title">启用</td>
<td class="title">启用字符编码</td>
<td>
<div class="ui checkbox">
<input type="checkbox" v-model="charsetConfig.isOn"/>

View File

@@ -20,11 +20,13 @@ Vue.component("http-compression-config-box", {
gzipRef: null,
deflateRef: null,
brotliRef: null,
minLength: {count: 0, "unit": "kb"},
maxLength: {count: 0, "unit": "kb"},
mimeTypes: ["text/*", "application/*", "font/*"],
minLength: {count: 1, "unit": "kb"},
maxLength: {count: 32, "unit": "mb"},
mimeTypes: ["text/*", "application/javascript", "application/json", "application/atom+xml", "application/rss+xml", "application/xhtml+xml", "font/*", "image/svg+xml"],
extensions: [".js", ".json", ".html", ".htm", ".xml", ".css", ".woff2", ".txt"],
conds: null
exceptExtensions: [".apk", ".ipa"],
conds: null,
enablePartialContent: false
}
}
@@ -108,6 +110,14 @@ Vue.component("http-compression-config-box", {
})
this.config.extensions = values
},
changeExceptExtensions: function (values) {
values.forEach(function (v, k) {
if (v.length > 0 && v[0] != ".") {
values[k] = "." + v
}
})
this.config.exceptExtensions = values
},
changeMimeTypes: function (values) {
this.config.mimeTypes = values
},
@@ -153,7 +163,7 @@ Vue.component("http-compression-config-box", {
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior">
<tr>
<td class="title">启用</td>
<td class="title">启用内容压缩</td>
<td>
<div class="ui checkbox">
<input type="checkbox" value="1" v-model="config.isOn"/>
@@ -179,6 +189,13 @@ Vue.component("http-compression-config-box", {
<p class="comment">含有这些扩展名的URL将会被压缩不区分大小写。</p>
</td>
</tr>
<tr>
<td>例外扩展名</td>
<td>
<values-box :values="config.exceptExtensions" @change="changeExceptExtensions" placeholder="比如 .html"></values-box>
<p class="comment">含有这些扩展名的URL将<strong>不会</strong>被压缩,不区分大小写。</p>
</td>
</tr>
<tr>
<td>支持的MimeType</td>
<td>
@@ -231,6 +248,13 @@ Vue.component("http-compression-config-box", {
<p class="comment">0表示不限制内容长度从文件尺寸或Content-Length中获取。</p>
</td>
</tr>
<tr>
<td>支持Partial<br/>Content</td>
<td>
<checkbox v-model="config.enablePartialContent"></checkbox>
<p class="comment">支持对分区内容PartialContent的压缩除非客户端有特殊要求一般不需要启用。</p>
</td>
</tr>
<tr>
<td>匹配条件</td>
<td>

View File

@@ -744,7 +744,7 @@ Vue.component("http-cond-params", {
<select class="ui dropdown auto-width" v-model="operator" @change="changeOperator">
<option v-for="operator in operators" :value="operator.op">{{operator.name}}</option>
</select>
<p class="comment">{{operatorDescription}}</p>
<p class="comment" v-html="operatorDescription"></p>
</div>
</td>
</tr>

View File

@@ -0,0 +1,34 @@
Vue.component("http-cors-header-config-box", {
props: ["value"],
data: function () {
let config = this.value
if (config == null) {
config = {
isOn: false,
allowMethods: [],
allowOrigin: "",
allowCredentials: true,
exposeHeaders: [],
maxAge: 0,
requestHeaders: [],
requestMethod: ""
}
}
return {
config: config
}
},
template: `<div>
<input type="hidden" name="corsJSON" :value="JSON.stringify(config)"/>
<table class="ui table definition selectable">
<tr>
<td class="title">启用CORS自适应跨域</td>
<td>
<checkbox v-model="config.isOn"></checkbox>
</td>
</tr>
</table>
<div class="margin"></div>
</div>`
})

View File

@@ -10,7 +10,21 @@ Vue.component("http-firewall-rule-label", {
showErr: function (err) {
teaweb.popupTip("规则校验错误,请修正:<span class=\"red\">" + teaweb.encodeHTML(err) + "</span>")
},
operatorName: function (operatorCode) {
var operatorName = operatorCode
if (typeof (window.WAF_RULE_OPERATORS) != null) {
window.WAF_RULE_OPERATORS.forEach(function (v) {
if (v.code == operatorCode) {
operatorName = v.name
}
})
}
return operatorName
},
isEmptyString: function (v) {
return typeof v == "string" && v.length == 0
}
},
template: `<div>
<div class="ui label tiny basic" style="line-height: 1.5">
@@ -29,8 +43,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>
<var :class="{dash:rule.isCaseInsensitive}" :title="rule.isCaseInsensitive ? '大小写不敏感':''" v-if="!rule.isComposed">{{rule.operator}}</var>
{{rule.value}}
<span :class="{dash:rule.isCaseInsensitive}" :title="rule.isCaseInsensitive ? '大小写不敏感':''" v-if="!rule.isComposed">{{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>
<!-- description -->

View File

@@ -14,15 +14,17 @@ Vue.component("http-firewall-rules-box", {
window.UPDATING_RULE = null
let that = this
teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, {
height: "30em",
callback: function (resp) {
that.rules.push(resp.data.rule)
}
})
},
updateRule: function (index, rule) {
window.UPDATING_RULE = rule
window.UPDATING_RULE = teaweb.clone(rule)
let that = this
teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, {
height: "30em",
callback: function (resp) {
Vue.set(that.rules, index, resp.data.rule)
}
@@ -33,12 +35,27 @@ Vue.component("http-firewall-rules-box", {
teaweb.confirm("确定要删除此规则吗?", function () {
that.rules.$remove(index)
})
},
operatorName: function (operatorCode) {
var operatorName = operatorCode
if (typeof (window.WAF_RULE_OPERATORS) != null) {
window.WAF_RULE_OPERATORS.forEach(function (v) {
if (v.code == operatorCode) {
operatorName = v.name
}
})
}
return operatorName
},
isEmptyString: function (v) {
return typeof v == "string" && v.length == 0
}
},
template: `<div>
<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">
<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}}]
<!-- cc2 -->
@@ -53,7 +70,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> <var>{{rule.operator}}</var> {{rule.value}}
<span v-if="rule.paramFilters != null && rule.paramFilters.length > 0" v-for="paramFilter in rule.paramFilters"> | {{paramFilter.code}}</span> <span :class="{dash:rule.isCaseInsensitive}" :title="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>
<!-- description -->

View File

@@ -28,7 +28,7 @@ Vue.component("http-header-assistant", {
}
this.matchedHeaders = this.allHeaders.filter(function (header) {
return teaweb.match(header, v)
}).slice(0, 5)
}).slice(0, 10)
}
},
methods: {
@@ -38,7 +38,7 @@ Vue.component("http-header-assistant", {
}
},
template: `<span v-if="selectedHeaderName.length == 0">
<a href="" v-for="header in matchedHeaders" class="ui label basic tiny blue" style="font-weight: normal" @click.prevent="select(header)">{{header}}</a>
<a href="" v-for="header in matchedHeaders" class="ui label basic tiny blue" style="font-weight: normal; margin-bottom: 0.3em" @click.prevent="select(header)">{{header}}</a>
<span v-if="matchedHeaders.length > 0">&nbsp; &nbsp;</span>
</span>`
})

View File

@@ -54,6 +54,13 @@ Vue.component("http-header-policy-box", {
}
}
let responseCORS = {
isOn: false
}
if (responsePolicy.cors != null) {
responseCORS = responsePolicy.cors
}
return {
type: type,
typeName: (type == "request") ? "请求" : "响应",
@@ -62,7 +69,8 @@ Vue.component("http-header-policy-box", {
requestSettingHeaders: requestSettingHeaders,
requestDeletingHeaders: requestDeletingHeaders,
responseSettingHeaders: responseSettingHeaders,
responseDeletingHeaders: responseDeletingHeaders
responseDeletingHeaders: responseDeletingHeaders,
responseCORS: responseCORS
}
},
methods: {
@@ -114,6 +122,13 @@ Vue.component("http-header-policy-box", {
.refresh()
}
)
},
updateCORS: function (policyId) {
teaweb.popup("/servers/server/settings/headers/updateCORSPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&type=" + this.type, {
callback: function () {
teaweb.successRefresh("保存成功")
}
})
}
},
template: `<div>
@@ -141,7 +156,7 @@ Vue.component("http-header-policy-box", {
<warning-message>由于已经在当前<a :href="vGroupSettingUrl + '#request'">服务分组</a>中进行了对应的配置,在这里的配置将不会生效。</warning-message>
</div>
<div :class="{'opacity-mask': vHasGroupRequestConfig}">
<h3>设置请求Header <a href="" @click.prevent="addSettingHeader(vRequestHeaderPolicy.id)">[添加新Header]</a></h3>
<h4>设置请求Header <a href="" @click.prevent="addSettingHeader(vRequestHeaderPolicy.id)">[添加新Header]</a></h4>
<p class="comment" v-if="requestSettingHeaders.length == 0">暂时还没有Header。</p>
<table class="ui table selectable celled" v-if="requestSettingHeaders.length > 0">
<thead>
@@ -151,35 +166,39 @@ Vue.component("http-header-policy-box", {
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="header in requestSettingHeaders">
<td class="five wide">
{{header.name}}
<div>
<span v-if="header.status != null && header.status.codes != null && !header.status.always"><grey-label v-for="code in header.status.codes" :key="code">{{code}}</grey-label></span>
<span v-if="header.methods != null && header.methods.length > 0"><grey-label v-for="method in header.methods" :key="method">{{method}}</grey-label></span>
<span v-if="header.domains != null && header.domains.length > 0"><grey-label v-for="domain in header.domains" :key="domain">{{domain}}</grey-label></span>
<grey-label v-if="header.shouldAppend">附加</grey-label>
<grey-label v-if="header.disableRedirect">跳转禁用</grey-label>
<grey-label v-if="header.shouldReplace && header.replaceValues != null && header.replaceValues.length > 0">替换</grey-label>
</div>
</td>
<td>{{header.value}}</td>
<td><a href="" @click.prevent="updateSettingPopup(vRequestHeaderPolicy.id, header.id)">修改</a> &nbsp; <a href="" @click.prevent="deleteHeader(vRequestHeaderPolicy.id, 'setHeader', header.id)">删除</a> </td>
</tr>
<tbody v-for="header in requestSettingHeaders">
<tr>
<td class="five wide">
{{header.name}}
<div>
<span v-if="header.status != null && header.status.codes != null && !header.status.always"><grey-label v-for="code in header.status.codes" :key="code">{{code}}</grey-label></span>
<span v-if="header.methods != null && header.methods.length > 0"><grey-label v-for="method in header.methods" :key="method">{{method}}</grey-label></span>
<span v-if="header.domains != null && header.domains.length > 0"><grey-label v-for="domain in header.domains" :key="domain">{{domain}}</grey-label></span>
<grey-label v-if="header.shouldAppend">附加</grey-label>
<grey-label v-if="header.disableRedirect">跳转禁用</grey-label>
<grey-label v-if="header.shouldReplace && header.replaceValues != null && header.replaceValues.length > 0">替换</grey-label>
</div>
</td>
<td>{{header.value}}</td>
<td><a href="" @click.prevent="updateSettingPopup(vRequestHeaderPolicy.id, header.id)">修改</a> &nbsp; <a href="" @click.prevent="deleteHeader(vRequestHeaderPolicy.id, 'setHeader', header.id)">删除</a> </td>
</tr>
</tbody>
</table>
<h3>删除请求Header</h3>
<h4>删除请求Header</h4>
<p class="comment">这里可以设置需要从请求中删除的Header。</p>
<table class="ui table definition selectable">
<td class="title">需要删除的Header</td>
<td>
<div v-if="requestDeletingHeaders.length > 0">
<div class="ui label small basic" v-for="headerName in requestDeletingHeaders">{{headerName}} <a href=""><i class="icon remove" title="删除" @click.prevent="deleteDeletingHeader(vRequestHeaderPolicy.id, headerName)"></i></a> </div>
<div class="ui divider" ></div>
</div>
<button class="ui button small" type="button" @click.prevent="addDeletingHeader(vRequestHeaderPolicy.id, 'request')">+</button>
</td>
<tr>
<td class="title">需要删除的Header</td>
<td>
<div v-if="requestDeletingHeaders.length > 0">
<div class="ui label small basic" v-for="headerName in requestDeletingHeaders">{{headerName}} <a href=""><i class="icon remove" title="删除" @click.prevent="deleteDeletingHeader(vRequestHeaderPolicy.id, headerName)"></i></a> </div>
<div class="ui divider" ></div>
</div>
<button class="ui button small" type="button" @click.prevent="addDeletingHeader(vRequestHeaderPolicy.id, 'request')">+</button>
</td>
</tr>
</table>
</div>
</div>
@@ -199,7 +218,7 @@ Vue.component("http-header-policy-box", {
<warning-message>由于已经在当前<a :href="vGroupSettingUrl + '#response'">服务分组</a>中进行了对应的配置,在这里的配置将不会生效。</warning-message>
</div>
<div :class="{'opacity-mask': vHasGroupResponseConfig}">
<h3>设置响应Header <a href="" @click.prevent="addSettingHeader(vResponseHeaderPolicy.id)">[添加新Header]</a></h3>
<h4>设置响应Header <a href="" @click.prevent="addSettingHeader(vResponseHeaderPolicy.id)">[添加新Header]</a></h4>
<p class="comment" style="margin-top: 0; padding-top: 0">将会覆盖已有的同名Header。</p>
<p class="comment" v-if="responseSettingHeaders.length == 0">暂时还没有Header。</p>
<table class="ui table selectable celled" v-if="responseSettingHeaders.length > 0">
@@ -210,37 +229,52 @@ Vue.component("http-header-policy-box", {
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="header in responseSettingHeaders">
<td class="five wide">
{{header.name}}
<div>
<span v-if="header.status != null && header.status.codes != null && !header.status.always"><grey-label v-for="code in header.status.codes" :key="code">{{code}}</grey-label></span>
<span v-if="header.methods != null && header.methods.length > 0"><grey-label v-for="method in header.methods" :key="method">{{method}}</grey-label></span>
<span v-if="header.domains != null && header.domains.length > 0"><grey-label v-for="domain in header.domains" :key="domain">{{domain}}</grey-label></span>
<grey-label v-if="header.shouldAppend">附加</grey-label>
<grey-label v-if="header.disableRedirect">跳转禁用</grey-label>
<grey-label v-if="header.shouldReplace && header.replaceValues != null && header.replaceValues.length > 0">替换</grey-label>
</div>
</td>
<td>{{header.value}}</td>
<td><a href="" @click.prevent="updateSettingPopup(vResponseHeaderPolicy.id, header.id)">修改</a> &nbsp; <a href="" @click.prevent="deleteHeader(vResponseHeaderPolicy.id, 'setHeader', header.id)">删除</a> </td>
</tr>
<tbody v-for="header in responseSettingHeaders">
<tr>
<td class="five wide">
{{header.name}}
<div>
<span v-if="header.status != null && header.status.codes != null && !header.status.always"><grey-label v-for="code in header.status.codes" :key="code">{{code}}</grey-label></span>
<span v-if="header.methods != null && header.methods.length > 0"><grey-label v-for="method in header.methods" :key="method">{{method}}</grey-label></span>
<span v-if="header.domains != null && header.domains.length > 0"><grey-label v-for="domain in header.domains" :key="domain">{{domain}}</grey-label></span>
<grey-label v-if="header.shouldAppend">附加</grey-label>
<grey-label v-if="header.disableRedirect">跳转禁用</grey-label>
<grey-label v-if="header.shouldReplace && header.replaceValues != null && header.replaceValues.length > 0">替换</grey-label>
</div>
</td>
<td>{{header.value}}</td>
<td><a href="" @click.prevent="updateSettingPopup(vResponseHeaderPolicy.id, header.id)">修改</a> &nbsp; <a href="" @click.prevent="deleteHeader(vResponseHeaderPolicy.id, 'setHeader', header.id)">删除</a> </td>
</tr>
</tbody>
</table>
<h3>删除响应Header</h3>
<h4>删除响应Header</h4>
<p class="comment">这里可以设置需要从响应中删除的Header。</p>
<table class="ui table definition selectable">
<td class="title">需要删除的Header</td>
<td>
<div v-if="responseDeletingHeaders.length > 0">
<div class="ui label small basic" v-for="headerName in responseDeletingHeaders">{{headerName}} <a href=""><i class="icon remove" title="删除" @click.prevent="deleteDeletingHeader(vResponseHeaderPolicy.id, headerName)"></i></a> </div>
<div class="ui divider" ></div>
</div>
<button class="ui button small" type="button" @click.prevent="addDeletingHeader(vResponseHeaderPolicy.id, 'response')">+</button>
</td>
<tr>
<td class="title">需要删除的Header</td>
<td>
<div v-if="responseDeletingHeaders.length > 0">
<div class="ui label small basic" v-for="headerName in responseDeletingHeaders">{{headerName}} <a href=""><i class="icon remove" title="删除" @click.prevent="deleteDeletingHeader(vResponseHeaderPolicy.id, headerName)"></i></a> </div>
<div class="ui divider" ></div>
</div>
<button class="ui button small" type="button" @click.prevent="addDeletingHeader(vResponseHeaderPolicy.id, 'response')">+</button>
</td>
</tr>
</table>
</div>
<h4>其他设置</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">CORS自适应跨域</td>
<td>
<span v-if="responseCORS.isOn" class="green">已启用</span><span class="disabled" v-else="">未启用</span> &nbsp; <a href="" @click.prevent="updateCORS(vResponseHeaderPolicy.id)">[修改]</a>
</td>
</tr>
</table>
</div>
</div>
<div class="margin"></div>
</div>`

View File

@@ -130,6 +130,7 @@ Vue.component("http-host-redirect-box", {
<div style="margin-top: 0.4em">
<grey-label><strong>域名跳转</strong></grey-label>
<grey-label v-if="redirect.domainAfterScheme != null && redirect.domainAfterScheme.length > 0">{{redirect.domainAfterScheme}}</grey-label>
<grey-label v-if="redirect.domainBeforeIgnorePorts">忽略端口</grey-label>
</div>
</div>
<div v-if="redirect.type == 'port'">

View File

@@ -57,7 +57,7 @@ Vue.component("http-remote-addr-config-box", {
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior">
<tr>
<td class="title">是否启用</td>
<td class="title">启用访客IP设置</td>
<td>
<div class="ui checkbox">
<input type="checkbox" value="1" v-model="config.isOn"/>

View File

@@ -62,7 +62,7 @@ Vue.component("http-request-limit-config-box", {
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior">
<tr>
<td class="title">启用</td>
<td class="title">启用请求限制</td>
<td>
<checkbox v-model="config.isOn"></checkbox>
</td>

View File

@@ -18,7 +18,7 @@ Vue.component("http-stat-config-box", {
<prior-checkbox :v-config="stat" v-if="vIsLocation || vIsGroup" ></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || stat.isPrior">
<tr>
<td class="title">是否开启统计</td>
<td class="title">启统计</td>
<td>
<div class="ui checkbox">
<input type="checkbox" v-model="stat.isOn"/>

View File

@@ -47,7 +47,7 @@ Vue.component("http-web-root-box", {
<prior-checkbox :v-config="rootConfig" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || rootConfig.isPrior">
<tr>
<td class="title">是否开启静态资源分发</td>
<td class="title">启静态资源分发</td>
<td>
<div class="ui checkbox">
<input type="checkbox" v-model="rootConfig.isOn"/>

View File

@@ -69,7 +69,7 @@ Vue.component("http-webp-config-box", {
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior">
<tr>
<td class="title">启用</td>
<td class="title">启用WebP压缩</td>
<td>
<div class="ui checkbox">
<input type="checkbox" value="1" v-model="config.isOn"/>

View File

@@ -80,7 +80,7 @@ Vue.component("http-websocket-box", {
<prior-checkbox :v-config="websocketRef" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="((!vIsLocation && !vIsGroup) || websocketRef.isPrior)">
<tr>
<td class="title">启用配置</td>
<td class="title">启用Websocket</td>
<td>
<div class="ui checkbox">
<input type="checkbox" v-model="websocketRef.isOn"/>

View File

@@ -36,7 +36,7 @@ Vue.component("script-config-box", {
<table class="ui table definition selectable">
<tbody>
<tr>
<td class="title">是否启用</td>
<td class="title">启用脚本设置</td>
<td><checkbox v-model="config.isOn"></checkbox></td>
</tr>
</tbody>

View File

@@ -43,6 +43,7 @@ Vue.component("ssl-config-box", {
}
let hsts = policy.hsts
let hstsMaxAgeString = "31536000"
if (hsts == null) {
hsts = {
isOn: false,
@@ -52,6 +53,9 @@ Vue.component("ssl-config-box", {
domains: []
}
}
if (hsts.maxAge != null) {
hstsMaxAgeString = hsts.maxAge.toString()
}
return {
policy: policy,
@@ -60,6 +64,7 @@ Vue.component("ssl-config-box", {
hsts: hsts,
hstsOptionsVisible: false,
hstsDomainAdding: false,
hstsMaxAgeString: hstsMaxAgeString,
addingHstsDomain: "",
hstsDomainEditingIndex: -1,
@@ -233,22 +238,22 @@ Vue.component("ssl-config-box", {
// 监控HSTS有效期修改
changeHSTSMaxAge: function () {
var v = this.hsts.maxAge
if (isNaN(v)) {
var v = parseInt(this.hstsMaxAgeString)
if (isNaN(v) || v < 0) {
this.hsts.maxAge = 0
this.hsts.days = "-"
return
}
this.hsts.days = parseInt(v / 86400)
if (isNaN(this.hsts.days)) {
this.hsts.days = "-"
} else if (this.hsts.days < 0) {
this.hsts.maxAge = v
this.hsts.days = v / 86400
if (this.hsts.days == 0) {
this.hsts.days = "-"
}
},
// 设置HSTS有效期
setHSTSMaxAge: function (maxAge) {
this.hsts.maxAge = maxAge
this.hstsMaxAgeString = maxAge.toString()
this.changeHSTSMaxAge()
},
@@ -441,7 +446,7 @@ Vue.component("ssl-config-box", {
<td>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="hstsMaxAge" v-model="hsts.maxAge" maxlength="10" size="10" @input="changeHSTSMaxAge()"/>
<input type="text" name="hstsMaxAge" v-model="hstsMaxAgeString" maxlength="10" size="10" @input="changeHSTSMaxAge()"/>
</div>
<div class="ui field">

View File

@@ -63,7 +63,7 @@ Vue.component("traffic-limit-config-box", {
<table class="ui table selectable definition">
<tbody>
<tr>
<td class="title">是否启用</td>
<td class="title">启用流量限制</td>
<td>
<checkbox v-model="config.isOn"></checkbox>
<p class="comment">注意由于流量统计是每5分钟统计一次所以超出流量限制后对用户的提醒也会有所延迟。</p>

View File

@@ -0,0 +1,142 @@
Vue.component("user-agent-config-box", {
props: ["v-is-location", "v-is-group", "value"],
data: function () {
let config = this.value
if (config == null) {
config = {
isPrior: false,
isOn: false,
filters: []
}
}
if (config.filters == null) {
config.filters = []
}
return {
config: config,
isAdding: false,
addingFilter: {
keywords: [],
action: "deny"
}
}
},
methods: {
isOn: function () {
return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn
},
remove: function (index) {
let that = this
teaweb.confirm("确定要删除此名单吗?", function () {
that.config.filters.$remove(index)
})
},
add: function () {
this.isAdding = true
},
confirm: function () {
if (this.addingFilter.action == "deny") {
this.config.filters.push(this.addingFilter)
} else {
let index = -1
this.config.filters.forEach(function (filter, filterIndex) {
if (filter.action == "allow") {
index = filterIndex
}
})
if (index < 0) {
this.config.filters.unshift(this.addingFilter)
} else {
this.config.filters.$insert(index + 1, this.addingFilter)
}
}
this.cancel()
},
cancel: function () {
this.isAdding = false
this.addingFilter = {
keywords: [],
action: "deny"
}
},
changeKeywords: function (keywords) {
this.addingFilter.keywords = keywords
}
},
template: `<div>
<input type="hidden" name="userAgentJSON" :value="JSON.stringify(config)"/>
<table class="ui table definition selectable">
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior">
<tr>
<td class="title">启用UA名单</td>
<td>
<div class="ui checkbox">
<input type="checkbox" value="1" v-model="config.isOn"/>
<label></label>
</div>
<p class="comment">选中后表示开启UserAgent名单。</p>
</td>
</tr>
</tbody>
<tbody v-show="isOn()">
<tr>
<td>UA名单</td>
<td>
<div v-if="config.filters.length > 0">
<table class="ui table celled">
<thead class="full-width">
<tr>
<th>UA关键词</th>
<th class="two wide">动作</th>
<th class="one op">操作</th>
</tr>
</thead>
<tbody v-for="(filter, index) in config.filters">
<tr>
<td style="background: white">
<span v-for="keyword in filter.keywords" class="ui label basic tiny">
<span v-if="keyword.length > 0">{{keyword}}</span>
<span v-if="keyword.length == 0" class="disabled">[空]</span>
</span>
</td>
<td>
<span v-if="filter.action == 'allow'" class="green">允许</span><span v-if="filter.action == 'deny'" class="red">不允许</span>
</td>
<td><a href="" @click.prevent="remove(index)">删除</a></td>
</tr>
</tbody>
</table>
</div>
<div v-if="isAdding" style="margin-top: 0.5em">
<table class="ui table definition">
<tr>
<td class="title">UA关键词</td>
<td>
<values-box :v-values="addingFilter.keywords" :v-allow-empty="true" @change="changeKeywords"></values-box>
<p class="comment">不区分大小写,比如<code-label>Chrome</code-label>;支持<code-label>*</code-label>通配符,比如<code-label>*Firefox*</code-label>也支持空的关键词表示空UserAgent。</p>
</td>
</tr>
<tr>
<td>动作</td>
<td><select class="ui dropdown auto-width" v-model="addingFilter.action">
<option value="deny">不允许</option>
<option value="allow">允许</option>
</select>
</td>
</tr>
</table>
<button type="button" class="ui button tiny" @click.prevent="confirm">保存</button> &nbsp; <a href="" @click.prevent="cancel" title="取消"><i class="icon remove small"></i></a>
</div>
<div v-show="!isAdding" style="margin-top: 0.5em">
<button class="ui button tiny" type="button" @click.prevent="add">+</button>
</div>
</td>
</tr>
</tbody>
</table>
<div class="margin"></div>
</div>`
})

View File

@@ -49,6 +49,10 @@ window.teaweb = {
}
return true;
},
clone: function (source) {
let s = JSON.stringify(source)
return JSON.parse(s)
},
loadJS: function (file, callback) {
let element = document.createElement("script")
@@ -85,7 +89,6 @@ window.teaweb = {
return
}
if (typeof (element) == "string") {
element = document.getElementById(element);
}
@@ -112,6 +115,8 @@ window.teaweb = {
},
reposition: !bottomLeft
})
element.picker = picker
},
formatBytes: function (bytes) {
bytes = Math.ceil(bytes);
@@ -348,6 +353,9 @@ window.teaweb = {
Swal: function () {
return this.isPopup() ? window.parent.Swal : window.Swal;
},
hasPopup: function () {
return document.getElementsByClassName("swal2-container").length > 0
},
success: function (message, callback) {
var width = "20em";
if (message.length > 30) {
@@ -939,7 +947,7 @@ window.teaweb = {
},
chartMap: {}, // dom id => chart
initChart: function (dom, cache) {
if (typeof(cache) != "boolean") {
if (typeof (cache) != "boolean") {
cache = true
}

View File

@@ -728,7 +728,7 @@ span.olive,
var.olive {
color: #b5cc18 !important;
}
var.dash {
span.dash {
border-bottom: 1px dashed grey;
}
var.normal {

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