Compare commits

...

54 Commits

Author SHA1 Message Date
刘祥超
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
刘祥超
567ffc80b6 优化<domains-box>组件 2022-11-02 09:58:59 +08:00
刘祥超
5d15a08ac8 防盗链使用<domains-box>组件 2022-11-02 09:47:17 +08:00
刘祥超
2b84037346 修改一处注释 2022-11-01 21:16:48 +08:00
刘祥超
536f11e617 修复datetime组件时间戳生成错误 2022-10-31 11:08:36 +08:00
刘祥超
5d367a384e 修改版本为0.5.7 2022-10-31 10:39:10 +08:00
99 changed files with 1038 additions and 518 deletions

1
docker/.gitignore vendored Normal file
View File

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

36
docker/Dockerfile Normal file
View File

@@ -0,0 +1,36 @@
FROM alpine:latest
LABEL maintainer="iwind.liu@gmail.com"
ENV TZ "Asia/Shanghai"
ENV VERSION 0.5.8
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"
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.6"
Version = "0.5.9"
APINodeVersion = "0.5.6"
APINodeVersion = "0.5.9"
ProductName = "Edge Admin"
ProcessName = "edge-admin"

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

@@ -122,6 +122,7 @@ func (this *IndexAction) RunPost(params struct {
AutoOpenPorts bool
ClockAutoSync bool
ClockServer string
ClockCheckChrony bool
AutoRemoteStart bool
AutoInstallNftables bool
@@ -154,6 +155,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

@@ -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, "-") ||

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

@@ -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,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

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

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

@@ -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

@@ -124,7 +124,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

View File

@@ -2392,9 +2392,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 {
@@ -3435,7 +3435,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"/>
@@ -4511,6 +4511,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'">
@@ -6679,7 +6680,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"/>
@@ -7275,7 +7276,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>)、域名后缀(以点号开头,如<code-label>.example.com</code-label>)和正则表达式(以波浪号开头,如<code-label>~.*.example.com</code-label>)。</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>
@@ -7352,14 +7353,14 @@ Vue.component("http-referers-config-box", {
<tr>
<td>允许的来源域名</td>
<td>
<values-box :values="config.allowDomains" @change="changeAllowDomains"></values-box>
<domains-box :v-domains="config.allowDomains" @change="changeAllowDomains">></domains-box>
<p class="comment">允许的其他来源域名列表,比如<code-label>example.com</code-label>、<code-label>*.example.com</code-label>。单个星号<code-label>*</code-label>表示允许所有域名。</p>
</td>
</tr>
<tr>
<td>禁止的来源域名</td>
<td>
<values-box :values="config.denyDomains" @change="changeDenyDomains"></values-box>
<domains-box :v-domains="config.denyDomains" @change="changeDenyDomains"></domains-box>
<p class="comment">禁止的来源域名列表,比如<code-label>example.org</code-label>、<code-label>*.example.org</code-label>;除了这些禁止的来源域名外,其他域名都会被允许,除非限定了允许的来源域名。</p>
</td>
</tr>
@@ -9106,7 +9107,7 @@ Vue.component("http-compression-config-box", {
brotliRef: null,
minLength: {count: 0, "unit": "kb"},
maxLength: {count: 0, "unit": "kb"},
mimeTypes: ["text/*", "application/*", "font/*"],
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
}
@@ -9237,7 +9238,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"/>
@@ -9432,7 +9433,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"/>
@@ -10063,7 +10064,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"/>
@@ -11180,7 +11181,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">
@@ -11446,7 +11447,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"/>
@@ -11585,7 +11586,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"/>
@@ -11848,6 +11849,7 @@ 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)
}
@@ -11857,6 +11859,7 @@ Vue.component("http-firewall-rules-box", {
window.UPDATING_RULE = 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)
}
@@ -13537,7 +13540,7 @@ Vue.component("firewall-syn-flood-config-box", {
<td class="title">启用</td>
<td>
<checkbox v-model="config.isOn"></checkbox>
<p class="comment">启用后WAF将会尝试自动检测并阻止SYN Flood攻击。此功能需要节点已安装并启用Firewalld。</p>
<p class="comment">启用后WAF将会尝试自动检测并阻止SYN Flood攻击。此功能需要节点已安装并启用nftables或Firewalld。</p>
</td>
</tr>
<tr>
@@ -14636,8 +14639,6 @@ Vue.component("datetime-input", {
},
methods: {
change: function () {
let date = new Date()
// day
if (!/^\d{4}-\d{1,2}-\d{1,2}$/.test(this.day)) {
this.hasDayError = true
@@ -14645,21 +14646,18 @@ Vue.component("datetime-input", {
}
let pieces = this.day.split("-")
let year = parseInt(pieces[0])
date.setFullYear(year)
let month = parseInt(pieces[1])
if (month < 1 || month > 12) {
this.hasDayError = true
return
}
date.setMonth(month - 1)
let day = parseInt(pieces[2])
if (day < 1 || day > 32) {
this.hasDayError = true
return
}
date.setDate(day)
this.hasDayError = false
@@ -14678,7 +14676,6 @@ Vue.component("datetime-input", {
return
}
this.hasHourError = false
date.setHours(hour)
// minute
if (!/^\d+$/.test(this.minute)) {
@@ -14695,7 +14692,6 @@ Vue.component("datetime-input", {
return
}
this.hasMinuteError = false
date.setMinutes(minute)
// second
if (!/^\d+$/.test(this.second)) {
@@ -14712,8 +14708,8 @@ Vue.component("datetime-input", {
return
}
this.hasSecondError = false
date.setSeconds(second)
let date = new Date(year, month - 1, day, hour, minute, second)
this.timestamp = Math.floor(date.getTime() / 1000)
},
leadingZero: function (s, l) {
@@ -15703,7 +15699,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: {
@@ -16340,6 +16336,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: {
@@ -16532,6 +16535,86 @@ Vue.component("finance-user-selector", {
</div>`
})
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>`
})
// 节点登录推荐端口
Vue.component("node-login-suggest-ports", {
data: function () {
@@ -17819,7 +17902,7 @@ window.REQUEST_COND_COMPONENTS = [{"type":"url-extension","name":"URL扩展名",
window.REQUEST_COND_OPERATORS = [{"description":"判断是否正则表达式匹配","name":"正则表达式匹配","op":"regexp"},{"description":"判断是否正则表达式不匹配","name":"正则表达式不匹配","op":"not regexp"},{"description":"使用字符串对比参数值是否相等于某个值","name":"字符串等于","op":"eq"},{"description":"参数值包含某个前缀","name":"字符串前缀","op":"prefix"},{"description":"参数值包含某个后缀","name":"字符串后缀","op":"suffix"},{"description":"参数值包含另外一个字符串","name":"字符串包含","op":"contains"},{"description":"参数值不包含另外一个字符串","name":"字符串不包含","op":"not contains"},{"description":"使用字符串对比参数值是否不相等于某个值","name":"字符串不等于","op":"not"},{"description":"判断参数值在某个列表中","name":"在列表中","op":"in"},{"description":"判断参数值不在某个列表中","name":"不在列表中","op":"not in"},{"description":"判断小写的扩展名(不带点)在某个列表中","name":"扩展名","op":"file ext"},{"description":"判断MimeType在某个列表中支持类似于image/*的语法","name":"MimeType","op":"mime type"},{"description":"判断版本号在某个范围内格式为version1,version2","name":"版本号范围","op":"version range"},{"description":"将参数转换为整数数字后进行对比","name":"整数等于","op":"eq int"},{"description":"将参数转换为可以有小数的浮点数字进行对比","name":"浮点数等于","op":"eq float"},{"description":"将参数转换为数字进行对比","name":"数字大于","op":"gt"},{"description":"将参数转换为数字进行对比","name":"数字大于等于","op":"gte"},{"description":"将参数转换为数字进行对比","name":"数字小于","op":"lt"},{"description":"将参数转换为数字进行对比","name":"数字小于等于","op":"lte"},{"description":"对整数参数值取模除数为10对比值为余数","name":"整数取模10","op":"mod 10"},{"description":"对整数参数值取模除数为100对比值为余数","name":"整数取模100","op":"mod 100"},{"description":"对整数参数值取模,对比值格式为:除数,余数比如10,1","name":"整数取模","op":"mod"},{"description":"将参数转换为IP进行对比","name":"IP等于","op":"eq ip"},{"description":"将参数转换为IP进行对比","name":"IP大于","op":"gt ip"},{"description":"将参数转换为IP进行对比","name":"IP大于等于","op":"gte ip"},{"description":"将参数转换为IP进行对比","name":"IP小于","op":"lt ip"},{"description":"将参数转换为IP进行对比","name":"IP小于等于","op":"lte ip"},{"description":"IP在某个范围之内范围格式可以是英文逗号分隔的ip1,ip2或者CIDR格式的ip/bits","name":"IP范围","op":"ip range"},{"description":"对IP参数值取模除数为10对比值为余数","name":"IP取模10","op":"ip mod 10"},{"description":"对IP参数值取模除数为100对比值为余数","name":"IP取模100","op":"ip mod 100"},{"description":"对IP参数值取模对比值格式为除数,余数比如10,1","name":"IP取模","op":"ip mod"},{"description":"判断参数值解析后的文件是否存在","name":"文件存在","op":"file exist"},{"description":"判断参数值解析后的文件是否不存在","name":"文件不存在","op":"file not exist"}]
window.REQUEST_VARIABLES = [{"code":"${edgeVersion}","description":"","name":"边缘节点版本"},{"code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取适合前端有别的反向代理服务时使用存在伪造的风险","name":"客户端地址IP"},{"code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","name":"客户端地址IP"},{"code":"${remotePort}","description":"","name":"客户端端口"},{"code":"${remoteUser}","description":"","name":"客户端用户名"},{"code":"${requestURI}","description":"比如/hello?name=lily","name":"请求URI"},{"code":"${requestPath}","description":"比如/hello","name":"请求路径(不包括参数)"},{"code":"${requestURL}","description":"比如https://example.com/hello?name=lily","name":"完整的请求URL"},{"code":"${requestLength}","description":"","name":"请求内容长度"},{"code":"${requestMethod}","description":"比如GET、POST","name":"请求方法"},{"code":"${requestFilename}","description":"","name":"请求文件路径"},{"code":"${scheme}","description":"","name":"请求协议http或https"},{"code":"${proto}","description:":"类似于HTTP/1.0","name":"包含版本的HTTP请求协议"},{"code":"${timeISO8601}","description":"比如2018-07-16T23:52:24.839+08:00","name":"ISO 8601格式的时间"},{"code":"${timeLocal}","description":"比如17/Jul/2018:09:52:24 +0800","name":"本地时间"},{"code":"${msec}","description":"比如1531756823.054","name":"带有毫秒的时间"},{"code":"${timestamp}","description":"","name":"unix时间戳单位为秒"},{"code":"${host}","description":"","name":"主机名"},{"code":"${serverName}","description":"","name":"接收请求的服务器名"},{"code":"${serverPort}","description":"","name":"接收请求的服务器端口"},{"code":"${referer}","description":"","name":"请求来源URL"},{"code":"${referer.host}","description":"","name":"请求来源URL域名"},{"code":"${userAgent}","description":"","name":"客户端信息"},{"code":"${contentType}","description":"","name":"请求头部的Content-Type"},{"code":"${cookies}","description":"","name":"所有cookie组合字符串"},{"code":"${cookie.NAME}","description":"","name":"单个cookie值"},{"code":"${isArgs}","description":"如果URL有参数则值为`?`;否则,则值为空","name":"问号(?)标记"},{"code":"${args}","description":"","name":"所有参数组合字符串"},{"code":"${arg.NAME}","description":"","name":"单个参数值"},{"code":"${headers}","description":"","name":"所有Header信息组合字符串"},{"code":"${header.NAME}","description":"","name":"单个Header值"},{"code":"${geo.country.name}","description":"","name":"国家/地区名称"},{"code":"${geo.country.id}","description":"","name":"国家/地区ID"},{"code":"${geo.province.name}","description":"目前只包含中国省份","name":"省份名称"},{"code":"${geo.province.id}","description":"目前只包含中国省份","name":"省份ID"},{"code":"${geo.city.name}","description":"目前只包含中国城市","name":"城市名称"},{"code":"${geo.city.id}","description":"目前只包含中国城市","name":"城市名称"},{"code":"${isp.name}","description":"","name":"ISP服务商名称"},{"code":"${isp.id}","description":"","name":"ISP服务商ID"},{"code":"${browser.os.name}","description":"客户端所在操作系统名称","name":"操作系统名称"},{"code":"${browser.os.version}","description":"客户端所在操作系统版本","name":"操作系统版本"},{"code":"${browser.name}","description":"客户端浏览器名称","name":"浏览器名称"},{"code":"${browser.version}","description":"客户端浏览器版本","name":"浏览器版本"},{"code":"${browser.isMobile}","description":"如果客户端是手机则值为1否则为0","name":"手机标识"}]
window.REQUEST_VARIABLES = [{"code":"${edgeVersion}","description":"","name":"边缘节点版本"},{"code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取适合前端有别的反向代理服务时使用存在伪造的风险","name":"客户端地址IP"},{"code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","name":"客户端地址IP"},{"code":"${remotePort}","description":"","name":"客户端端口"},{"code":"${remoteUser}","description":"","name":"客户端用户名"},{"code":"${requestURI}","description":"比如/hello?name=lily","name":"请求URI"},{"code":"${requestPath}","description":"比如/hello","name":"请求路径(不包括参数)"},{"code":"${requestURL}","description":"比如https://example.com/hello?name=lily","name":"完整的请求URL"},{"code":"${requestLength}","description":"","name":"请求内容长度"},{"code":"${requestMethod}","description":"比如GET、POST","name":"请求方法"},{"code":"${requestFilename}","description":"","name":"请求文件路径"},{"code":"${scheme}","description":"","name":"请求协议http或https"},{"code":"${proto}","description:":"类似于HTTP/1.0","name":"包含版本的HTTP请求协议"},{"code":"${timeISO8601}","description":"比如2018-07-16T23:52:24.839+08:00","name":"ISO 8601格式的时间"},{"code":"${timeLocal}","description":"比如17/Jul/2018:09:52:24 +0800","name":"本地时间"},{"code":"${msec}","description":"比如1531756823.054","name":"带有毫秒的时间"},{"code":"${timestamp}","description":"","name":"unix时间戳单位为秒"},{"code":"${host}","description":"","name":"主机名"},{"code":"${cname}","description":"比如38b48e4f.goedge.cn","name":"当前网站的CNAME"},{"code":"${serverName}","description":"","name":"接收请求的服务器名"},{"code":"${serverPort}","description":"","name":"接收请求的服务器端口"},{"code":"${referer}","description":"","name":"请求来源URL"},{"code":"${referer.host}","description":"","name":"请求来源URL域名"},{"code":"${userAgent}","description":"","name":"客户端信息"},{"code":"${contentType}","description":"","name":"请求头部的Content-Type"},{"code":"${cookies}","description":"","name":"所有cookie组合字符串"},{"code":"${cookie.NAME}","description":"","name":"单个cookie值"},{"code":"${isArgs}","description":"如果URL有参数则值为`?`;否则,则值为空","name":"问号(?)标记"},{"code":"${args}","description":"","name":"所有参数组合字符串"},{"code":"${arg.NAME}","description":"","name":"单个参数值"},{"code":"${headers}","description":"","name":"所有Header信息组合字符串"},{"code":"${header.NAME}","description":"","name":"单个Header值"},{"code":"${geo.country.name}","description":"","name":"国家/地区名称"},{"code":"${geo.country.id}","description":"","name":"国家/地区ID"},{"code":"${geo.province.name}","description":"目前只包含中国省份","name":"省份名称"},{"code":"${geo.province.id}","description":"目前只包含中国省份","name":"省份ID"},{"code":"${geo.city.name}","description":"目前只包含中国城市","name":"城市名称"},{"code":"${geo.city.id}","description":"目前只包含中国城市","name":"城市名称"},{"code":"${isp.name}","description":"","name":"ISP服务商名称"},{"code":"${isp.id}","description":"","name":"ISP服务商ID"},{"code":"${browser.os.name}","description":"客户端所在操作系统名称","name":"操作系统名称"},{"code":"${browser.os.version}","description":"客户端所在操作系统版本","name":"操作系统版本"},{"code":"${browser.name}","description":"客户端浏览器名称","name":"浏览器名称"},{"code":"${browser.version}","description":"客户端浏览器版本","name":"浏览器版本"},{"code":"${browser.isMobile}","description":"如果客户端是手机则值为1否则为0","name":"手机标识"}]
window.METRIC_HTTP_KEYS = [{"name":"客户端地址IP","code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取适用于前端可能有别的反向代理的情形存在被伪造的可能","icon":""},{"name":"直接客户端地址IP","code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","icon":""},{"name":"客户端用户名","code":"${remoteUser}","description":"通过基本认证填入的用户名","icon":""},{"name":"请求URI","code":"${requestURI}","description":"包含参数,比如/hello?name=lily","icon":""},{"name":"请求路径","code":"${requestPath}","description":"不包含参数,比如/hello","icon":""},{"name":"完整URL","code":"${requestURL}","description":"比如https://example.com/hello?name=lily","icon":""},{"name":"请求方法","code":"${requestMethod}","description":"比如GET、POST等","icon":""},{"name":"请求协议Scheme","code":"${scheme}","description":"http或https","icon":""},{"name":"文件扩展名","code":"${requestPathExtension}","description":"请求路径中的文件扩展名,包括点符号,比如.html、.png","icon":""},{"name":"主机名","code":"${host}","description":"通常是请求的域名","icon":""},{"name":"请求协议Proto","code":"${proto}","description":"包含版本的HTTP请求协议类似于HTTP/1.0","icon":""},{"name":"HTTP协议","code":"${proto}","description":"包含版本的HTTP请求协议类似于HTTP/1.0","icon":""},{"name":"URL参数值","code":"${arg.NAME}","description":"单个URL参数值","icon":""},{"name":"请求来源URL","code":"${referer}","description":"请求来源Referer URL","icon":""},{"name":"请求来源URL域名","code":"${referer.host}","description":"请求来源Referer URL域名","icon":""},{"name":"Header值","code":"${header.NAME}","description":"单个Header值比如${header.User-Agent}","icon":""},{"name":"Cookie值","code":"${cookie.NAME}","description":"单个cookie值比如${cookie.sid}","icon":""},{"name":"状态码","code":"${status}","description":"","icon":""},{"name":"响应的Content-Type值","code":"${response.contentType}","description":"","icon":""}]

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

@@ -54,8 +54,6 @@ Vue.component("datetime-input", {
},
methods: {
change: function () {
let date = new Date()
// day
if (!/^\d{4}-\d{1,2}-\d{1,2}$/.test(this.day)) {
this.hasDayError = true
@@ -63,21 +61,18 @@ Vue.component("datetime-input", {
}
let pieces = this.day.split("-")
let year = parseInt(pieces[0])
date.setFullYear(year)
let month = parseInt(pieces[1])
if (month < 1 || month > 12) {
this.hasDayError = true
return
}
date.setMonth(month - 1)
let day = parseInt(pieces[2])
if (day < 1 || day > 32) {
this.hasDayError = true
return
}
date.setDate(day)
this.hasDayError = false
@@ -96,7 +91,6 @@ Vue.component("datetime-input", {
return
}
this.hasHourError = false
date.setHours(hour)
// minute
if (!/^\d+$/.test(this.minute)) {
@@ -113,7 +107,6 @@ Vue.component("datetime-input", {
return
}
this.hasMinuteError = false
date.setMinutes(minute)
// second
if (!/^\d+$/.test(this.second)) {
@@ -130,8 +123,8 @@ Vue.component("datetime-input", {
return
}
this.hasSecondError = false
date.setSeconds(second)
let date = new Date(year, month - 1, day, hour, minute, second)
this.timestamp = Math.floor(date.getTime() / 1000)
},
leadingZero: function (s, l) {

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

@@ -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>)、域名后缀(以点号开头,如<code-label>.example.com</code-label>)和正则表达式(以波浪号开头,如<code-label>~.*.example.com</code-label>)。</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

@@ -59,7 +59,7 @@ Vue.component("firewall-syn-flood-config-box", {
<td class="title">启用</td>
<td>
<checkbox v-model="config.isOn"></checkbox>
<p class="comment">启用后WAF将会尝试自动检测并阻止SYN Flood攻击。此功能需要节点已安装并启用Firewalld。</p>
<p class="comment">启用后WAF将会尝试自动检测并阻止SYN Flood攻击。此功能需要节点已安装并启用nftables或Firewalld。</p>
</td>
</tr>
<tr>

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

@@ -22,7 +22,7 @@ Vue.component("http-compression-config-box", {
brotliRef: null,
minLength: {count: 0, "unit": "kb"},
maxLength: {count: 0, "unit": "kb"},
mimeTypes: ["text/*", "application/*", "font/*"],
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
}
@@ -153,7 +153,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"/>

View File

@@ -14,6 +14,7 @@ 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)
}
@@ -23,6 +24,7 @@ Vue.component("http-firewall-rules-box", {
window.UPDATING_RULE = 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)
}

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

@@ -65,14 +65,14 @@ Vue.component("http-referers-config-box", {
<tr>
<td>允许的来源域名</td>
<td>
<values-box :values="config.allowDomains" @change="changeAllowDomains"></values-box>
<domains-box :v-domains="config.allowDomains" @change="changeAllowDomains">></domains-box>
<p class="comment">允许的其他来源域名列表,比如<code-label>example.com</code-label>、<code-label>*.example.com</code-label>。单个星号<code-label>*</code-label>表示允许所有域名。</p>
</td>
</tr>
<tr>
<td>禁止的来源域名</td>
<td>
<values-box :values="config.denyDomains" @change="changeDenyDomains"></values-box>
<domains-box :v-domains="config.denyDomains" @change="changeDenyDomains"></domains-box>
<p class="comment">禁止的来源域名列表,比如<code-label>example.org</code-label>、<code-label>*.example.org</code-label>;除了这些禁止的来源域名外,其他域名都会被允许,除非限定了允许的来源域名。</p>
</td>
</tr>

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

@@ -85,7 +85,6 @@ window.teaweb = {
return
}
if (typeof (element) == "string") {
element = document.getElementById(element);
}
@@ -112,6 +111,8 @@ window.teaweb = {
},
reposition: !bottomLeft
})
element.picker = picker
},
formatBytes: function (bytes) {
bytes = Math.ceil(bytes);

View File

@@ -3,5 +3,5 @@
<span class="item">|</span>
<menu-item :href="'/admins/admin?adminId=' + admin.id" code="index">"{{admin.fullname}}" 详情</menu-item>
<menu-item :href="'/admins/update?adminId=' + admin.id" code="update">修改</menu-item>
<menu-item :href="'/admins/accessKeys?adminId=' + admin.id" code="accessKey">API AccessKey({{admin.countAccessKeys}})</menu-item>
<menu-item :href="'/admins/accesskeys?adminId=' + admin.id" code="accessKey">API AccessKey({{admin.countAccessKeys}})</menu-item>
</first-menu>

View File

@@ -1,6 +1,6 @@
Tea.context(function () {
this.createAccessKey = function () {
teaweb.popup("/admins/accessKeys/createPopup?adminId=" + this.admin.id, {
teaweb.popup("/admins/accesskeys/createPopup?adminId=" + this.admin.id, {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()

View File

@@ -41,10 +41,10 @@
</td>
</tr>
<tr v-if="restIsOn">
<td class="color-border">HTTP API监听端口</td>
<td class="color-border">HTTP API监听端口 *</td>
<td>
<network-addresses-box :v-name="'restListensJSON'" :v-server-type="'httpWeb'" @change="changeRestListens"></network-addresses-box>
<p class="comment">HTTP API节点进程监听的网络端口。</p>
<p class="comment">HTTP API节点进程监听的网络端口需要和当前节点的GRPC端口不同</p>
</td>
</tr>
<tr>
@@ -54,7 +54,7 @@
</td>
</tr>
<tr>
<td>是否启用</td>
<td>启用当前节点</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" checked="checked"/>

View File

@@ -18,7 +18,7 @@ Tea.context(function () {
this.hasHTTPS = this.grpcAddrs.$any(function (k, v) {
return v.protocol == "https"
}) || (this.node.restIsOn && this.restAddrs.$any(function (k, v) {
}) || (this.restIsOn && this.restAddrs.$any(function (k, v) {
return v.protocol == "https"
}))
}

View File

@@ -46,10 +46,10 @@
</td>
</tr>
<tr v-if="node.restIsOn">
<td class="color-border">HTTP API监听端口</td>
<td class="color-border">HTTP API监听端口 *</td>
<td>
<network-addresses-box :v-name="'restListensJSON'" :v-server-type="'httpWeb'" @change="changeRestListens" :v-addresses="node.restListens"></network-addresses-box>
<p class="comment">HTTP API节点进程监听的网络端口。</p>
<p class="comment">HTTP API节点进程监听的网络端口需要和当前节点的GRPC端口不同</p>
</td>
</tr>
<tr>
@@ -66,7 +66,7 @@
</td>
</tr>
<tr>
<td>启用</td>
<td>启用当前节点</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="node.isOn"/>

View File

@@ -2,7 +2,9 @@
<menu-item :href="'/clusters/cluster?clusterId=' + clusterId">{{currentClusterName}}</menu-item>
<span class="disabled item" style="padding: 0">&raquo;</span>
<menu-item :href="'/clusters/cluster/nodes?clusterId=' + clusterId" code="index">节点列表</menu-item>
<menu-item :href="'/clusters/cluster/createNode?clusterId=' + clusterId" code="create">创建节点</menu-item>
<span class="disabled item">|</span>
<menu-item :href="'/clusters/cluster/createNode?clusterId=' + clusterId" code="create">[创建节点]</menu-item>
<span class="disabled item">|</span>
<menu-item :href="'/clusters/cluster/installManual?clusterId=' + clusterId" code="install">安装升级</menu-item>
<menu-item :href="'/clusters/cluster/groups?clusterId=' + clusterId" code="group">节点分组</menu-item>
</second-menu>

View File

@@ -23,14 +23,14 @@
<tr>
<td class="title">节点名称 *</td>
<td>
<input type="text" name="name" maxlength="50" ref="focus"/>
<input type="text" name="name" maxlength="50" ref="focus" v-model="name" @input="changeName"/>
</td>
</tr>
<tr>
<td>IP地址 *</td>
<td>
<node-ip-addresses-box></node-ip-addresses-box>
<p class="comment">用于访问节点和域名解析等。</p>
<p class="comment">用于访问节点和域名解析等<span v-if="defaultIP.length > 0"><strong>,如果没有填写默认为{{defaultIP}}</strong></span></p>
</td>
</tr>
<tr v-if="dnsRoutes.length > 0">

View File

@@ -5,6 +5,7 @@ Tea.context(function () {
this.sshPort = ""
this.grantId = 0
this.step = "info"
this.name = ""
this.success = function (resp) {
this.node = resp.data.node
@@ -170,4 +171,32 @@ Tea.context(function () {
this.createNext = function () {
teaweb.reload()
}
this.defaultIP = ""
this.changeName = function () {
if (this.validateIP(this.name)) {
this.defaultIP = this.name
} else {
this.defaultIP = ""
}
}
this.validateIP = function (v) {
// 目前只支持ipv4
let pieces = v.split(".")
if (pieces.length != 4) {
return false
}
for (let i = 0; i < pieces.length; i++) {
if (!/^\d{1,3}$/.test(pieces[i])) {
return false
}
let p = parseInt(pieces[i], 10)
if (p > 255) {
return false
}
}
return true
}
})

View File

@@ -4,7 +4,7 @@
{$template "/code_editor"}
<div class="right-box">
<p>在官网下载节点安装包,然后通过修改节点安装包中的<code-label>configs/cluster.yaml</code-label>,启动后会自动注册节点。</p>
<p>在官网下载节点安装包,然后通过修改节点安装包中的<code-label>configs/cluster.yaml</code-label>(如果此配置文件尚未创建,你需要先创建)为以下内容,启动后会自动注册节点。</p>
<table class="ui table definition selectable">
<tr>
<td class="title">cluster.yaml<br/>

View File

@@ -143,13 +143,6 @@
</span>
</td>
</tr>
<tr>
<td>CPU线程数</td>
<td>
<span v-if="node.maxCPU > 0">{{node.maxCPU}}线程</span>
<span v-else class="disabled">没有限制。</span>
</td>
</tr>
<tr>
<td>缓存磁盘容量</td>
<td>
@@ -167,6 +160,22 @@
<size-capacity-view :v-value="node.maxCacheMemoryCapacity"></size-capacity-view>
</div>
</td>
</tr>
<tr>
<td>CPU线程数</td>
<td>
<span v-if="node.maxCPU > 0">{{node.maxCPU}}线程</span>
<span v-else class="disabled">没有限制。</span>
</td>
</tr>
<tr>
<td>API节点地址</td>
<td>
<div v-if="node.apiNodeAddrs != null && node.apiNodeAddrs.length > 0">
<span v-for="addr in node.apiNodeAddrs" class="ui label basic small">{{addr}}</span>
</div>
<span v-else class="disabled">使用全局设置</span>
</td>
</tr>
</tbody>
</table>
@@ -226,11 +235,28 @@
<td>主程序位置</td>
<td>{{node.status.exePath}}</td>
</tr>
<tr>
<td>最近API连接状况</td>
<td>
<span v-if="node.status.apiSuccessPercent > 0 && node.status.apiAvgCostSeconds > 0">
<span v-if="node.status.apiSuccessPercent <= 50" class="red">连接错误异常严重({{round(100 - node.status.apiSuccessPercent)}}%失败请改善当前节点和API节点之间通讯</span>
<span v-else-if="node.status.apiSuccessPercent <= 80" class="red">连接错误较多({{round(100 - node.status.apiSuccessPercent)}}%失败请改善当前节点和API节点之间通讯</span>
<span v-else-if="node.status.apiSuccessPercent < 100" class="orange">有连接错误发生({{round(100 - node.status.apiSuccessPercent)}}%失败请改善当前节点和API节点之间通讯</span>
<span v-else>
<span v-if="node.status.apiAvgCostSeconds <= 1" class="green">连接良好</span>
<span v-else-if="node.status.apiAvgCostSeconds <= 5" class="orange">连接基本稳定(平均{{round(node.status.apiAvgCostSeconds)}}秒)</span>
<span v-else-if="node.status.apiAvgCostSeconds <= 10" class="orange">连接速度较慢(平均{{round(node.status.apiAvgCostSeconds)}}秒)</span>
<span v-else class="red">连接非常慢(平均{{round(node.status.apiAvgCostSeconds)}}秒请改善当前节点和API节点之间通讯</span>
</span>
</span>
<span v-else class="disabled">尚未上报数据</span>
</td>
</tr>
<tr v-if="nodeDatetime.length > 0">
<td>上次更新时间</td>
<td>
{{nodeDatetime}}
<p class="comment" v-if="nodeTimeDiff > 3"><span class="red">当前节点时间与API节点时间相差 {{nodeTimeDiff}} 秒,请同步节点时间。</span></p>
<p class="comment" v-if="nodeTimeDiff > 30"><span class="red">当前节点时间与API节点时间相差 {{nodeTimeDiff}} 秒,请同步节点时间。</span></p>
</td>
</tr>
</tbody>

View File

@@ -32,4 +32,8 @@ Tea.context(function () {
this.isStopping = false
})
}
this.round = function (f) {
return Math.round(f * 100) / 100
}
})

View File

@@ -15,10 +15,17 @@
</td>
</tr>
<tr>
<td>磁盘缓存目录</td>
<td>磁盘缓存目录</td>
<td>
<input type="text" name="cacheDiskDir" maxlength="500" value="" v-model="node.cacheDiskDir"/>
<p class="comment">存放文件缓存的目录,通常填写绝对路径。不填则表示使用集群缓存策略中定义的目录。</p>
<p class="comment">存放文件缓存的目录,通常填写绝对路径。不填则表示使用集群缓存策略中定义的目录。</p>
</td>
</tr>
<tr>
<td>其他磁盘缓存目录</td>
<td>
<node-cache-disk-dirs-box name="cacheDiskSubDirsJSON" v-model="node.cacheDiskSubDirs"></node-cache-disk-dirs-box>
<p class="comment">除了主目录外,可以在这里添加别的可用于缓存的目录;缓存目录有变更时(添加、删除或修改)需要手动将这些缓存目录清空。</p>
</td>
</tr>
<tr>

View File

@@ -13,7 +13,7 @@
<td class="title">SSH主机地址</td>
<td>
<input type="text" name="sshHost" maxlength="64" v-model="sshHost"/>
<p class="comment"><span v-if="hostIsAutoFilled"><strong>已自动填充,需要保存</strong></span>比如192.168.1.100。</p>
<p class="comment"><span v-if="hostIsAutoFilled" class="red"><strong>已自动填充</strong>,需要点击"保存"按钮后生效</span>比如192.168.1.100。</p>
</td>
</tr>
<tr>
@@ -31,7 +31,7 @@
</tr>
</table>
<div class="ui message" v-if="isTesting">正在测试是否连接 ...</div>
<div class="ui message" v-if="isTesting">正在测试连接 ...</div>
<div class="ui message green" v-if="resp != null && resp.isOk">连接成功!</div>
<div class="ui message red" v-if="resp != null && !resp.isOk">连接失败:{{resp.error}}</div>

View File

@@ -19,6 +19,19 @@
<h4>DNS解析</h4>
<dns-resolver-config-box :v-dns-resolver-config="dnsResolverConfig"></dns-resolver-config-box>
<h4>API相关</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">API节点地址</td>
<td>
<div style="margin-bottom: 0.5em">
<api-node-addresses-box :v-name="'apiNodeAddrsJSON'" :v-addrs="apiNodeAddrs"></api-node-addresses-box>
</div>
<p class="comment">当前节点单独使用的API节点设置。<pro-warning-label></pro-warning-label></p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>
</div>

View File

@@ -19,4 +19,17 @@ a.small {
font-size: 0.8em;
text-align: center;
}
.node-name-td {
position: relative;
}
.node-name-td .icon.setting {
display: none;
position: absolute;
right: 1em;
top: 50%;
margin-top: -1em;
}
.node-name-td:hover .icon.setting {
display: inline;
}
/*# sourceMappingURL=nodes.css.map */

View File

@@ -1 +1 @@
{"version":3,"sources":["nodes.less"],"names":[],"mappings":"AAAA,MAAO;EACN,oBAAA;;AAGD,CACC;EACC,iCAAA;;AAIF,WACC;EACC,kBAAA;EACA,aAAA;;AAIF,WAAW,UACV;EACC,eAAA;EACA,cAAA;;AAIF,WAAW,MACV;EACC,eAAA;;AAIF,CAAC;EACA,gBAAA;EACA,kBAAA","file":"nodes.css"}
{"version":3,"sources":["nodes.less"],"names":[],"mappings":"AAAA,MAAO;EACN,oBAAA;;AAGD,CACC;EACC,iCAAA;;AAIF,WACC;EACC,kBAAA;EACA,aAAA;;AAIF,WAAW,UACV;EACC,eAAA;EACA,cAAA;;AAIF,WAAW,MACV;EACC,eAAA;;AAIF,CAAC;EACA,gBAAA;EACA,kBAAA;;AAGD;EACC,kBAAA;;AADD,aAGC,MAAK;EACJ,aAAA;EACA,kBAAA;EACA,UAAA;EACA,QAAA;EACA,gBAAA;;AAIF,aAAa,MACZ,MAAK;EACJ,eAAA","file":"nodes.css"}

View File

@@ -73,7 +73,10 @@
</tr>
</thead>
<tr v-for="node in nodes">
<td><a :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id">{{node.name}}<sup v-if="node.level > 1"><span class="blue"> &nbsp;L{{node.level}}</span></sup></a>
<td class="node-name-td"><a :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id">{{node.name}}<sup v-if="node.level > 1"><span class="blue"> &nbsp;L{{node.level}}</span></sup></a>
<a :href="'/clusters/cluster/node/update?clusterId=' + clusterId + '&nodeId=' + node.id" title="设置"><i class="icon setting grey"></i></a>
<div v-if="node.region != null">
<grey-label>区域:{{node.region.name}}</grey-label>
</div>
@@ -118,7 +121,7 @@
<span v-else class="disabled">-</span>
</td>
<td class="center">
<span v-if="node.status.isActive && node.status.trafficOutBytes > 0">{{teaweb.formatBytes(node.status.trafficOutBytes/60)}}/s</span>
<span v-if="node.status.isActive && node.status.trafficOutBytes > 0">{{teaweb.formatBits(node.status.trafficOutBytes * 8/60)}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">

View File

@@ -31,4 +31,22 @@ a {
a.small {
font-size: 0.8em;
text-align: center;
}
.node-name-td {
position: relative;
.icon.setting {
display: none;
position: absolute;
right: 1em;
top: 50%;
margin-top: -1em;
}
}
.node-name-td:hover {
.icon.setting {
display: inline;
}
}

View File

@@ -85,6 +85,13 @@
<p class="comment">可选项。默认使用<code-label>pool.ntp.org</code-label></p>
</td>
</tr>
<tr v-show="cluster.clock.autoSync">
<td class="color-border">检查Chrony</td>
<td>
<checkbox name="clockCheckChrony" v-model="cluster.clock.checkChrony"></checkbox>
<p class="comment">选中后表示如果chrony另一款时间同步软件正在运行则不重复执行时间同步。</p>
</td>
</tr>
<tr>
<td>自动远程启动</td>
<td>

View File

@@ -15,7 +15,17 @@
.cluster-name-td .icon.opacity {
opacity: 0.3;
}
.cluster-name-td .icon.setting {
display: none;
position: absolute;
right: 3em;
top: 50%;
margin-top: -0.7em;
}
.cluster-name-td:hover .icon.pin {
display: inline;
}
.cluster-name-td:hover .icon.setting {
display: inline;
}
/*# sourceMappingURL=index.css.map */

View File

@@ -1 +1 @@
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA;EACC,kBAAA;;AADD,gBAGC,MAAK;EACJ,aAAA;EACA,kBAAA;EACA,UAAA;EACA,QAAA;EACA,kBAAA;EACA,YAAA;;AATF,gBAYC,MAAK;EACJ,eAAA;;AAbF,gBAgBC,MAAK;EACJ,YAAA;;AAIF,gBAAgB,MACf,MAAK;EACJ,eAAA","file":"index.css"}
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA;EACC,kBAAA;;AADD,gBAGC,MAAK;EACJ,aAAA;EACA,kBAAA;EACA,UAAA;EACA,QAAA;EACA,kBAAA;EACA,YAAA;;AATF,gBAYC,MAAK;EACJ,eAAA;;AAbF,gBAgBC,MAAK;EACJ,YAAA;;AAjBF,gBAoBC,MAAK;EACJ,aAAA;EACA,kBAAA;EACA,UAAA;EACA,QAAA;EACA,kBAAA;;AAIF,gBAAgB,MACf,MAAK;EACJ,eAAA;;AAFF,gBAAgB,MAIf,MAAK;EACJ,eAAA","file":"index.css"}

View File

@@ -43,6 +43,9 @@
<tr v-for="cluster in clusters">
<td class="cluster-name-td">
<a :href="'/clusters/cluster?clusterId=' + cluster.id"><keyword :v-word="keyword">{{cluster.name}}</keyword></a>
<a :href="'/clusters/cluster/settings?clusterId=' + cluster.id" title="设置"><i class="icon setting grey"></i></a>
<div v-if="cluster.timeZone != null && cluster.timeZone.length > 0">
<grey-label>时区:{{cluster.timeZone}}</grey-label>
</div>

View File

@@ -17,10 +17,21 @@
.icon.opacity {
opacity: 0.3;
}
.icon.setting {
display: none;
position: absolute;
right: 3em;
top: 50%;
margin-top: -0.7em;
}
}
.cluster-name-td:hover {
.icon.pin {
display: inline;
}
}
.icon.setting {
display: inline;
}
}

View File

@@ -0,0 +1,14 @@
.node-name-td {
position: relative;
}
.node-name-td .icon.setting {
display: none;
position: absolute;
right: 1em;
top: 50%;
margin-top: -1em;
}
.node-name-td:hover .icon.setting {
display: inline;
}
/*# sourceMappingURL=nodes.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["nodes.less"],"names":[],"mappings":"AAAA;EACC,kBAAA;;AADD,aAGC,MAAK;EACJ,aAAA;EACA,kBAAA;EACA,UAAA;EACA,QAAA;EACA,gBAAA;;AAIF,aAAa,MACZ,MAAK;EACJ,eAAA","file":"nodes.css"}

View File

@@ -67,7 +67,10 @@
</tr>
</thead>
<tr v-for="node in nodes">
<td><a :href="'/clusters/cluster/node?clusterId=' + clusterId + '&nodeId=' + node.id">{{node.name}}<sup v-if="node.level > 1"><span class="blue"> &nbsp;L{{node.level}}</span></sup></a>
<td class="node-name-td"><a :href="'/clusters/cluster/node?clusterId=' + node.cluster.id + '&nodeId=' + node.id">{{node.name}}<sup v-if="node.level > 1"><span class="blue"> &nbsp;L{{node.level}}</span></sup></a>
<a :href="'/clusters/cluster/node/update?clusterId=' + node.cluster.id + '&nodeId=' + node.id" title="设置"><i class="icon setting grey"></i></a>
<div v-if="node.region != null">
<grey-label>区域:{{node.region.name}}</grey-label>
</div>
@@ -112,7 +115,7 @@
<span v-else class="disabled">-</span>
</td>
<td class="center">
<span v-if="node.status.isActive && node.status.trafficOutBytes > 0">{{teaweb.formatBytes(node.status.trafficOutBytes/60)}}/s</span>
<span v-if="node.status.isActive && node.status.trafficOutBytes > 0">{{teaweb.formatBits(node.status.trafficOutBytes * 8/60)}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">

View File

@@ -0,0 +1,17 @@
.node-name-td {
position: relative;
.icon.setting {
display: none;
position: absolute;
right: 1em;
top: 50%;
margin-top: -1em;
}
}
.node-name-td:hover {
.icon.setting {
display: inline;
}
}

View File

@@ -1,7 +1,6 @@
{$layout}
<div class="ui tabular menu tiny">
<a href="" class="item" :class="{active: tab == 'domainMatch'}" @click.prevent="selectTab('domainMatch')">域名匹配配置</a>
<a href="" class="item" :class="{active: tab == 'domainAuditing'}" @click.prevent="selectTab('domainAuditing')">域名审核配置</a>
<a href="" class="item" :class="{active: tab == 'tcpPorts'}" @click.prevent="selectTab('tcpPorts')">TCP/TLS端口</a>
</div>
@@ -9,11 +8,6 @@
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="globalConfigJSON" :value="JSON.stringify(globalConfig)"/>
<!-- 域名相关配置 -->
<div v-show="tab == 'domainMatch'">
<p class="comment">域名匹配相关配置已经转移到集群设置中,请到对应的集群设置中修改。</p>
</div>
<!-- 域名审核相关配置 -->
<div v-show="tab == 'domainAuditing'">
<table class="ui table definition selectable">

View File

@@ -1,9 +1,9 @@
Tea.context(function () {
this.tab = "domainMatch"
this.tab = "domainAuditing"
this.$delay(function () {
if (window.location.hash != null && window.location.hash.length > 1) {
this.selectTab(window.location.hash.substr(1))
this.selectTab(window.location.hash.substring(1))
}
})

View File

@@ -91,7 +91,7 @@
</tr>
<tr>
<td>
<span v-if="rule.operator == 'match' || rule.operator == 'not match'">正则表达式</span>
<span v-if="(checkpoint == null || checkpoint.dataType != 'bool') && (rule.operator == 'match' || rule.operator == 'not match')">正则表达式</span>
<span v-else>对比值</span>
</td>
<td>
@@ -105,6 +105,15 @@
<p class="comment">将二进制进行Base64Encode后放在这里比如<code-label>Hello</code-label>对应<code-label>SGVsbG8=</code-label></p>
</div>
<!-- bool数据 -->
<div v-else-if="checkpoint != null && checkpoint.dataType == 'bool'">
<select name="value" class="ui selectable auto-width" v-model="rule.value" @change="changeRuleValue">
<option value="">[请选择]</option>
<option value="1">是(1)</option>
<option value="0">否(0)</option>
</select>
</div>
<!-- 其余数据 -->
<textarea rows="3" maxlength="4096" name="value" v-model="rule.value" @input="changeRuleValue" v-else></textarea>
@@ -132,7 +141,7 @@
</div>
</td>
</tr>
<tr v-if="rule.operator == 'match' || rule.operator == 'not match'">
<tr v-if="(checkpoint == null || checkpoint.dataType != 'bool') && (rule.operator == 'match' || rule.operator == 'not match')">
<td>正则表达式测试</td>
<td>
<a href="" v-if="!regexpTestIsOn" @click.prevent="changeRegexpTestIsOn">[输入测试字符串]</a>
@@ -147,7 +156,7 @@
</div>
</td>
</tr>
<tr v-if="operator.case != 'none'">
<tr v-if="(checkpoint == null || checkpoint.dataType != 'bool') && operator != null && operator.case != 'none'">
<td>不区分大小写</td>
<td>
<div class="ui checkbox">

View File

@@ -52,6 +52,19 @@ Tea.context(function () {
this.checkpoint = this.checkpoints.$find(function (k, v) {
return v.prefix == that.rule.checkpointPrefix
})
if (this.checkpoint == null) {
return
}
switch (this.checkpoint.dataType) {
case "bool":
this.rule.operator = "eq"
break
case "number":
this.rule.operator = "eq"
break
default:
this.rule.operator = "match"
}
}
@@ -63,6 +76,9 @@ Tea.context(function () {
this.operator = this.operators.$find(function (k, v) {
return v.code == that.rule.operator
})
if (this.operator == null) {
return
}
if (!this.isUpdating) {
this.rule.isCaseInsensitive = (this.operator.case == "yes")
}

View File

@@ -34,7 +34,7 @@ Tea.context(function () {
this.createSet = function (groupId) {
teaweb.popup("/servers/components/waf/createSetPopup?firewallPolicyId=" + this.firewallPolicyId + "&groupId=" + groupId + "&type=" + this.type, {
width: "50em",
height: "30em",
height: "40em",
callback: function () {
teaweb.success("保存成功", function () {
window.location.reload()
@@ -47,7 +47,7 @@ Tea.context(function () {
this.updateSet = function (setId) {
teaweb.popup("/servers/components/waf/updateSetPopup?firewallPolicyId=" + this.firewallPolicyId + "&groupId=" + this.group.id + "&type=" + this.type + "&setId=" + setId, {
width: "50em",
height: "30em",
height: "40em",
callback: function () {
teaweb.success("保存成功", function () {
window.location.reload()

View File

@@ -35,7 +35,7 @@ Tea.context(function () {
this.createSet = function (groupId) {
teaweb.popup("/servers/components/waf/createSetPopup?firewallPolicyId=" + this.firewallPolicyId + "&groupId=" + groupId + "&type=" + this.type, {
width: "50em",
height: "30em",
height: "40em",
callback: function () {
teaweb.success("保存成功", function () {
window.location.reload()
@@ -48,7 +48,7 @@ Tea.context(function () {
this.updateSet = function (setId) {
teaweb.popup("/servers/components/waf/updateSetPopup?firewallPolicyId=" + this.firewallPolicyId + "&groupId=" + this.group.id + "&type=" + this.type + "&setId=" + setId, {
width: "50em",
height: "30em",
height: "40em",
callback: function () {
teaweb.success("保存成功", function () {
window.location.reload()

View File

@@ -63,7 +63,7 @@
<th>部署集群</th>
<th>域名</th>
<th>端口</th>
<th class="center" style="width: 8em">下行带宽<tip-icon content="最近5分钟峰值带宽每5分钟更新一次,单位:比特"></tip-icon><sort-arrow name="trafficOutOrder"></sort-arrow></th>
<th class="center" style="width: 8em">下行带宽<tip-icon content="最近5分钟峰值带宽每5分钟更新一次"></tip-icon><sort-arrow name="trafficOutOrder"></sort-arrow></th>
<th class="two wide center">状态</th>
<th class="two op">操作</th>
</tr>

View File

@@ -10,7 +10,7 @@
<tr>
<td class="title">当前服务CNAME</td>
<td>
<span id="cname-text">{{dnsName}}.<span v-if="dnsDomain.length > 0">{{dnsDomain}}.</span><span v-else>根域名</span></span> &nbsp; <copy-to-clipboard :v-target="'cname-text'"></copy-to-clipboard> &nbsp;<a href="" @click.prevent="regenerateCNAME()" style="font-size: 0.8em">[重新生成]</a> &nbsp; <a href="" @click.prevent="updateCNAME()" style="font-size: 0.8em">[手动修改]</a>
<span id="cname-text">{{dnsName}}.<span v-if="dnsDomain.length > 0">{{dnsDomain}}</span><span v-else>根域名</span></span> &nbsp; <copy-to-clipboard :v-target="'cname-text'"></copy-to-clipboard> &nbsp;<a href="" @click.prevent="regenerateCNAME()" style="font-size: 0.8em">[重新生成]</a> &nbsp; <a href="" @click.prevent="updateCNAME()" style="font-size: 0.8em">[手动修改]</a>
<p class="comment">你需要为你的每个<a :href="'/servers/server/settings/serverNames?serverId=' + serverId">网站域名</a>设置一个CNAME解析值为上面内容。</p>
</td>
</tr>

View File

@@ -8,7 +8,7 @@
<tr>
<td class="title">证书包含的域名 *</td>
<td>
<span v-if="serverNames.length == 0" class="disabled">还没有设置域名,暂时不能申请。</span>
<span v-if="serverNames.length == 0" class="disabled">还没有需要申请证书的域名,暂时不能申请。</span>
<div v-if="serverNames.length > 0">
<div v-for="(serverName, index) in serverNames" class="ui tiny basic label">
<input type="hidden" name="serverNames" :value="serverName"/>

View File

@@ -80,6 +80,13 @@
<domains-box name="domainsBeforeJSON" :v-domains="redirect.domainsBefore"></domains-box>
</td>
</tr>
<tr>
<td class="color-border">忽略跳转前端口</td>
<td>
<checkbox name="domainBeforeIgnorePorts" v-model="redirect.domainBeforeIgnorePorts"></checkbox>
<p class="comment">选中后,表示忽略跳转前域名端口,只要域名匹配时就跳转;如不选中,则表示只有域名和端口同时匹配时才会跳转。</p>
</td>
</tr>
<tr>
<td class="color-border">跳转后域名 *</td>
<td>

View File

@@ -21,6 +21,7 @@ Tea.context(function () {
domainsAll: false,
domainBefore: [],
domainBeforeIgnorePorts: true,
domainAfter: "",
domainAfterScheme: "",

View File

@@ -35,7 +35,7 @@ Tea.context(function () {
this.createSet = function (groupId) {
teaweb.popup("/servers/components/waf/createSetPopup?firewallPolicyId=" + this.firewallPolicyId + "&groupId=" + groupId + "&type=" + this.type, {
width: "50em",
height: "30em",
height: "40em",
callback: function () {
teaweb.success("保存成功", function () {
window.location.reload()
@@ -48,7 +48,7 @@ Tea.context(function () {
this.updateSet = function (setId) {
teaweb.popup("/servers/components/waf/updateSetPopup?firewallPolicyId=" + this.firewallPolicyId + "&groupId=" + this.group.id + "&type=" + this.type + "&setId=" + setId, {
width: "50em",
height: "30em",
height: "40em",
callback: function () {
teaweb.success("保存成功", function () {
window.location.reload()

View File

@@ -0,0 +1,69 @@
.install-box {
width: 50em;
position: fixed;
left: 50%;
margin-left: -25em;
top: 1em;
bottom: 1em;
overflow-y: auto;
}
.install-box .button.margin {
margin-top: 1em;
}
.install-box .button.primary {
float: right;
}
.install-box .button.disabled {
float: right;
}
.install-box table td.title {
width: 10em;
}
.install-box .radio {
margin-right: 1em;
}
.install-box .radio label {
cursor: pointer !important;
font-size: 0.9em !important;
}
.install-box h3 {
font-weight: normal;
}
.install-box .content-box {
overflow-y: auto;
position: fixed;
top: 5em;
bottom: 5em;
left: 50%;
width: 50em;
padding-right: 1em;
margin-left: -25em;
z-index: 1;
}
.install-box .content-box::-webkit-scrollbar {
width: 4px;
}
.install-box .button-group {
position: fixed;
left: 50%;
margin-left: -25em;
z-index: 1;
width: 50em;
bottom: 1em;
}
.install-box .button-group button {
z-index: 10;
}
.install-box .button-group .status-box {
position: absolute;
top: 3em;
left: 5em;
right: 5em;
bottom: 0;
text-align: center;
z-index: 0;
}
.install-box::-webkit-scrollbar {
width: 4px;
}
/*# sourceMappingURL=@install.css.map */

View File

@@ -1 +1 @@
undefined
{"version":3,"sources":["@install.less"],"names":[],"mappings":"AAAA;EAIC,WAAA;EACA,eAAA;EACA,SAAA;EACA,kBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;;AAVD,YAYC,QAAO;EACN,eAAA;;AAbF,YAgBC,QAAO;EACN,YAAA;;AAjBF,YAoBC,QAAO;EACN,YAAA;;AArBF,YAwBC,MACC,GAAE;EACD,WAAA;;AA1BH,YA8BC;EACC,iBAAA;;AA/BF,YA8BC,OAGC;EACC,0BAAA;EACA,gBAAA;;AAnCH,YAuCC;EACC,mBAAA;;AAxCF,YA2CC;EACC,gBAAA;EACA,eAAA;EACA,QAAA;EACA,WAAA;EACA,SAAA;EACA,WAAA;EACA,kBAAA;EACA,kBAAA;EACA,UAAA;;AApDF,YAuDC,aAAY;EACX,UAAA;;AAxDF,YA2DC;EACC,eAAA;EACA,SAAA;EACA,kBAAA;EACA,UAAA;EACA,WAAA;EACA,WAAA;;AAjEF,YA2DC,cAQC;EACC,WAAA;;AApEH,YA2DC,cAYC;EACC,kBAAA;EACA,QAAA;EACA,SAAA;EACA,UAAA;EACA,SAAA;EACA,kBAAA;EACA,UAAA;;AAKH,YAAY;EACX,UAAA","file":"@install.css"}

View File

@@ -1,10 +1,11 @@
.install-box {
@width: 50em;
@half-width: 25em;
width: @width;
position: fixed;
left: 50%;
margin-left: -@width/2;
margin-left: -@half-width;
top: 1em;
bottom: 1em;
overflow-y: auto;
@@ -48,7 +49,7 @@
left: 50%;
width: @width;
padding-right: 1em;
margin-left: -@width/2;
margin-left: -@half-width;
z-index: 1;
}
@@ -59,7 +60,7 @@
.button-group {
position: fixed;
left: 50%;
margin-left: -@width/2;
margin-left: -@half-width;
z-index: 1;
width: @width;
bottom: 1em;
@@ -71,8 +72,8 @@
.status-box {
position: absolute;
top: 3em;
left: 15em;
right: 15em;
left: 5em;
right: 5em;
bottom: 0;
text-align: center;
z-index: 0;

View File

@@ -57,8 +57,8 @@
.install-box .button-group .status-box {
position: absolute;
top: 3em;
left: 15em;
right: 15em;
left: 5em;
right: 5em;
bottom: 0;
text-align: center;
z-index: 0;

View File

@@ -1 +1 @@
{"version":3,"sources":["@install.less"],"names":[],"mappings":"AAAA;EAGC,WAAA;EACA,eAAA;EACA,SAAA;EACA,kBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;;AATD,YAWC,QAAO;EACN,eAAA;;AAZF,YAeC,QAAO;EACN,YAAA;;AAhBF,YAmBC,QAAO;EACN,YAAA;;AApBF,YAuBC,MACC,GAAE;EACD,WAAA;;AAzBH,YA6BC;EACC,iBAAA;;AA9BF,YA6BC,OAGC;EACC,0BAAA;EACA,2BAAA;;AAlCH,YAsCC;EACC,mBAAA;;AAvCF,YA0CC;EACC,gBAAA;EACA,eAAA;EACA,QAAA;EACA,WAAA;EACA,SAAA;EACA,WAAA;EACA,kBAAA;EACA,kBAAA;EACA,UAAA;;AAnDF,YAsDC,aAAY;EACX,UAAA;;AAvDF,YA0DC;EACC,eAAA;EACA,SAAA;EACA,kBAAA;EACA,UAAA;EACA,WAAA;EACA,WAAA;;AAhEF,YA0DC,cAQC;EACC,WAAA;;AAnEH,YA0DC,cAYC;EACC,kBAAA;EACA,QAAA;EACA,UAAA;EACA,WAAA;EACA,SAAA;EACA,kBAAA;EACA,UAAA;;AAKH,YAAY;EACX,UAAA","file":"index.css"}
{"version":3,"sources":["@install.less"],"names":[],"mappings":"AAAA;EAIC,WAAA;EACA,eAAA;EACA,SAAA;EACA,kBAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;;AAVD,YAYC,QAAO;EACN,eAAA;;AAbF,YAgBC,QAAO;EACN,YAAA;;AAjBF,YAoBC,QAAO;EACN,YAAA;;AArBF,YAwBC,MACC,GAAE;EACD,WAAA;;AA1BH,YA8BC;EACC,iBAAA;;AA/BF,YA8BC,OAGC;EACC,0BAAA;EACA,gBAAA;;AAnCH,YAuCC;EACC,mBAAA;;AAxCF,YA2CC;EACC,gBAAA;EACA,eAAA;EACA,QAAA;EACA,WAAA;EACA,SAAA;EACA,WAAA;EACA,kBAAA;EACA,kBAAA;EACA,UAAA;;AApDF,YAuDC,aAAY;EACX,UAAA;;AAxDF,YA2DC;EACC,eAAA;EACA,SAAA;EACA,kBAAA;EACA,UAAA;EACA,WAAA;EACA,WAAA;;AAjEF,YA2DC,cAQC;EACC,WAAA;;AApEH,YA2DC,cAYC;EACC,kBAAA;EACA,QAAA;EACA,SAAA;EACA,UAAA;EACA,SAAA;EACA,kBAAA;EACA,UAAA;;AAKH,YAAY;EACX,UAAA","file":"index.css"}

View File

@@ -47,10 +47,10 @@
<!-- 介绍 -->
<div v-show="step == STEP_INTRO">
<div>感谢你选择使用<strong>GoEdge</strong>集群服务系统,下面让我们一起开始配置系统。</div>
<div class="margin">在这之前如果你还没有可用的MySQL数据库请先安装数据库再进行。</div>
<div>感谢你选择使用<strong>GoEdge</strong>CDN系统,下面让我们一起开始配置系统。</div>
<div class="margin">在这之前如果你还没有可用的MySQL数据库请先安装MySQL数据库再进行。</div>
<div class="margin" style="color: grey">免责声明GoEdge软件开发者并不对您的软件使用方法、服务对象、服务内容负道德或法律上的约束责任在使用本软件时产生的一切法律风险自负。</div>
<div class="margin" style="color: grey">用户协议请在遵守中华人民共和国政策、法律、法规的前提下使用本软件自愿承担因不当使用本软件产生的一切法律后果承认GoEdge开发者拥有本软件的著作权点击"开始"安装表示你同意此用户协议。</div>
<div class="margin" style="color: grey">用户协议请在遵守中华人民共和国政策、法律、法规的前提下使用本软件自愿承担因不当使用本软件产生的一切法律后果承认GoEdge开发者拥有本软件的所有著作权;点击"开始"安装表示你同意此用户协议。</div>
<button class="ui button primary" style="margin-top: 10em" type="button" @click.prevent="goIntroNext()">开始<i class="icon long arrow right"></i></button>
</div>

View File

@@ -1,2 +1 @@
@import "@install";
@import "@install";

View File

@@ -6,5 +6,5 @@
<menu-item :href="'/users/update?userId=' + user.id" code="update">修改</menu-item>
<menu-item :href="'/users/features?userId=' + user.id" code="feature" v-if="teaIsPlus">功能</menu-item>
<menu-item :href="'/users/identity?userId=' + user.id" code="identity" v-if="teaIsPlus">实名认证<span v-if="user.hasNewIndividualIdentity || user.hasNewEnterpriseIdentity" class="red small">(待审核)</span><span v-if="user.identityTag != null && user.identityTag.length > 0" class="green">({{user.identityTag}})</span></menu-item>
<menu-item :href="'/users/accessKeys?userId=' + user.id" code="accessKey">API AccessKey({{user.countAccessKeys}})</menu-item>
<menu-item :href="'/users/accesskeys?userId=' + user.id" code="accessKey">API AccessKey({{user.countAccessKeys}})</menu-item>
</first-menu>

View File

@@ -1,6 +1,6 @@
Tea.context(function () {
this.createAccessKey = function () {
teaweb.popup("/users/accessKeys/createPopup?userId=" + this.user.id, {
teaweb.popup("/users/accesskeys/createPopup?userId=" + this.user.id, {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()