Compare commits

...

88 Commits

Author SHA1 Message Date
刘祥超
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
刘祥超
04933e6bf0 重置配置的同时也重置本地API节点的配置 2022-10-30 20:06:59 +08:00
刘祥超
12abe9aa69 优化界面显示 2022-10-30 20:06:38 +08:00
刘祥超
cba642a4bc 多处域名列表支持批量输入 2022-10-28 17:59:46 +08:00
刘祥超
9fce0ac0aa 优化API节点界面 2022-10-27 19:45:03 +08:00
刘祥超
681812b619 优化申请证书界面 2022-10-27 19:44:50 +08:00
刘祥超
6d0be57698 更新components.js 2022-10-27 11:19:10 +08:00
刘祥超
1d521602e1 节点SSH登录自动使用集群设置 2022-10-26 19:23:50 +08:00
刘祥超
2f67e7937a 集群全局服务配置中增加多个访问日志相关选项 2022-10-26 17:49:25 +08:00
刘祥超
e0078a42dc URL跳转中增加域名跳转、端口跳转 2022-10-26 16:06:50 +08:00
刘祥超
3ec875d49d 节点设置中增加“通过IP名单”选项 2022-10-26 10:41:42 +08:00
刘祥超
35028d1310 开源版本Dashboard不再显示用户节点相关信息 2022-10-26 09:51:09 +08:00
刘祥超
7ba3d7c4bb 修复窄屏显示时有些内容无法隐藏的问题 2022-10-26 09:30:41 +08:00
刘祥超
c05e64098c 更新components.js 2022-10-25 14:36:40 +08:00
刘祥超
e82ee56a2c IP名单支持模糊查询 2022-10-25 10:15:17 +08:00
刘祥超
5681b61aea 在选择区域弹出框中可以不选区域直接确定返回 2022-10-25 09:43:31 +08:00
刘祥超
d6ce7eab25 修复域名解析--集群中单节点多IP时无法修改IP的Bug 2022-10-24 16:33:56 +08:00
刘祥超
bf597fe41c 添加、修改、删除HTTP Header时增加通用Header提示 2022-10-24 15:42:18 +08:00
刘祥超
6a920f964f 集群服务设置--访问日志中可以设置是否只记录通用Header 2022-10-24 14:39:32 +08:00
刘祥超
977b66ba4e 防盗链功能增加禁止的来源域名 2022-10-24 10:20:44 +08:00
刘祥超
4659c29358 优化界面显示 2022-10-23 11:44:09 +08:00
刘祥超
e3bc95b275 管理安全设置中域名列表支持通配符 2022-10-21 18:36:33 +08:00
刘祥超
bf51255e13 优化区域列表界面 2022-10-20 15:17:03 +08:00
刘祥超
915fe6837b 区域设置中增加节点快速设置区域的功能 2022-10-20 15:11:26 +08:00
刘祥超
d1237215c0 更新components.js 2022-10-20 11:07:07 +08:00
刘祥超
8ba5cfdfa6 优化代码 2022-10-19 19:55:51 +08:00
刘祥超
0d1097425d 优化界面和文字 2022-10-18 10:31:20 +08:00
刘祥超
0789a9ecc8 优化数字格式化 2022-10-16 09:36:36 +08:00
刘祥超
400f764b74 优化数字格式化 2022-10-15 19:33:07 +08:00
刘祥超
46e036e3a4 删除代码 2022-10-15 16:58:45 +08:00
刘祥超
17e6264af8 删除不需要的代码 2022-10-14 09:57:24 +08:00
刘祥超
468b6ae125 添加源站的时候也提示8443和8080对应的协议 2022-10-07 09:01:22 +08:00
刘祥超
e5f5ee4f6a 提交components.js 2022-10-03 21:11:02 +08:00
刘祥超
3695082ec2 服务列表页带宽使用比特代替字节 2022-10-03 19:25:20 +08:00
刘祥超
f384d86014 版本修改为0.5.6 2022-10-01 08:49:00 +08:00
刘祥超
34bf5028c3 删除不必要的代码 2022-10-01 07:16:37 +08:00
刘祥超
8b727aa939 提交components.js 2022-09-30 18:36:46 +08:00
刘祥超
9432600de6 优化网站服务列表页在海量域名时的加载速度 2022-09-30 13:48:16 +08:00
刘祥超
bb790ec687 文件下载链接找不到对应元素时不提示错误 2022-09-30 09:39:59 +08:00
刘祥超
8bad658d7c 版本调整为v0.5.5 2022-09-28 18:57:47 +08:00
202 changed files with 3808 additions and 2398 deletions

View File

@@ -1,6 +1,7 @@
package main package main
import ( import (
"bytes"
"fmt" "fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/apps" "github.com/TeaOSLab/EdgeAdmin/internal/apps"
"github.com/TeaOSLab/EdgeAdmin/internal/configs" "github.com/TeaOSLab/EdgeAdmin/internal/configs"
@@ -9,10 +10,13 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/nodes" "github.com/TeaOSLab/EdgeAdmin/internal/nodes"
"github.com/TeaOSLab/EdgeAdmin/internal/utils" "github.com/TeaOSLab/EdgeAdmin/internal/utils"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web" _ "github.com/TeaOSLab/EdgeAdmin/internal/web"
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap" _ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/gosock/pkg/gosock" "github.com/iwind/gosock/pkg/gosock"
"log" "log"
"os"
"os/exec"
"time" "time"
) )
@@ -53,6 +57,20 @@ func main() {
fmt.Println("[ERROR]reset failed: " + err.Error()) fmt.Println("[ERROR]reset failed: " + err.Error())
return return
} }
// reset local api
var apiNodeExe = Tea.Root + "/edge-api/bin/edge-api"
_, err = os.Stat(apiNodeExe)
if err == nil {
var cmd = exec.Command(apiNodeExe, "reset")
var stderr = &bytes.Buffer{}
cmd.Stderr = stderr
err = cmd.Run()
if err != nil {
fmt.Println("reset api node failed: " + stderr.String())
}
}
fmt.Println("done") fmt.Println("done")
}) })
app.On("recover", func() { app.On("recover", func() {

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.7
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}

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

@@ -61,10 +61,11 @@ func LoadAPIConfig() (*APIConfig, error) {
// ResetAPIConfig 重置配置 // ResetAPIConfig 重置配置
func ResetAPIConfig() error { func ResetAPIConfig() error {
filename := "api.yaml" var filename = "api.yaml"
// 重置 configs/api.yaml
{ {
configFile := Tea.ConfigFile(filename) var configFile = Tea.ConfigFile(filename)
stat, err := os.Stat(configFile) stat, err := os.Stat(configFile)
if err == nil && !stat.IsDir() { if err == nil && !stat.IsDir() {
err = os.Remove(configFile) err = os.Remove(configFile)
@@ -77,7 +78,7 @@ func ResetAPIConfig() error {
// 重置 ~/.edge-admin/api.yaml // 重置 ~/.edge-admin/api.yaml
homeDir, homeErr := os.UserHomeDir() homeDir, homeErr := os.UserHomeDir()
if homeErr == nil { if homeErr == nil {
configFile := homeDir + "/." + teaconst.ProcessName + "/" + filename var configFile = homeDir + "/." + teaconst.ProcessName + "/" + filename
stat, err := os.Stat(configFile) stat, err := os.Stat(configFile)
if err == nil && !stat.IsDir() { if err == nil && !stat.IsDir() {
err = os.Remove(configFile) err = os.Remove(configFile)
@@ -89,7 +90,7 @@ func ResetAPIConfig() error {
// 重置 /etc/edge-admin/api.yaml // 重置 /etc/edge-admin/api.yaml
{ {
configFile := "/etc/" + teaconst.ProcessName + "/" + filename var configFile = "/etc/" + teaconst.ProcessName + "/" + filename
stat, err := os.Stat(configFile) stat, err := os.Stat(configFile)
if err == nil && !stat.IsDir() { if err == nil && !stat.IsDir() {
err = os.Remove(configFile) err = os.Remove(configFile)

View File

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

View File

@@ -98,10 +98,6 @@ func (this *RPCClient) NodeRegionRPC() pb.NodeRegionServiceClient {
return pb.NewNodeRegionServiceClient(this.pickConn()) return pb.NewNodeRegionServiceClient(this.pickConn())
} }
func (this *RPCClient) NodePriceItemRPC() pb.NodePriceItemServiceClient {
return pb.NewNodePriceItemServiceClient(this.pickConn())
}
func (this *RPCClient) NodeIPAddressRPC() pb.NodeIPAddressServiceClient { func (this *RPCClient) NodeIPAddressRPC() pb.NodeIPAddressServiceClient {
return pb.NewNodeIPAddressServiceClient(this.pickConn()) return pb.NewNodeIPAddressServiceClient(this.pickConn())
} }
@@ -399,26 +395,6 @@ func (this *RPCClient) UserRPC() pb.UserServiceClient {
return pb.NewUserServiceClient(this.pickConn()) return pb.NewUserServiceClient(this.pickConn())
} }
func (this *RPCClient) UserBillRPC() pb.UserBillServiceClient {
return pb.NewUserBillServiceClient(this.pickConn())
}
func (this *RPCClient) ServerBillRPC() pb.ServerBillServiceClient {
return pb.NewServerBillServiceClient(this.pickConn())
}
func (this *RPCClient) UserAccountRPC() pb.UserAccountServiceClient {
return pb.NewUserAccountServiceClient(this.pickConn())
}
func (this *RPCClient) UserAccountLogRPC() pb.UserAccountLogServiceClient {
return pb.NewUserAccountLogServiceClient(this.pickConn())
}
func (this *RPCClient) UserAccountDailyStatRPC() pb.UserAccountDailyStatServiceClient {
return pb.NewUserAccountDailyStatServiceClient(this.pickConn())
}
func (this *RPCClient) UserAccessKeyRPC() pb.UserAccessKeyServiceClient { func (this *RPCClient) UserAccessKeyRPC() pb.UserAccessKeyServiceClient {
return pb.NewUserAccessKeyServiceClient(this.pickConn()) return pb.NewUserAccessKeyServiceClient(this.pickConn())
} }
@@ -475,14 +451,6 @@ func (this *RPCClient) ServerStatBoardChartRPC() pb.ServerStatBoardChartServiceC
return pb.NewServerStatBoardChartServiceClient(this.pickConn()) return pb.NewServerStatBoardChartServiceClient(this.pickConn())
} }
func (this *RPCClient) PlanRPC() pb.PlanServiceClient {
return pb.NewPlanServiceClient(this.pickConn())
}
func (this *RPCClient) UserPlanRPC() pb.UserPlanServiceClient {
return pb.NewUserPlanServiceClient(this.pickConn())
}
func (this *RPCClient) TrafficDailyStatRPC() pb.TrafficDailyStatServiceClient { func (this *RPCClient) TrafficDailyStatRPC() pb.TrafficDailyStatServiceClient {
return pb.NewTrafficDailyStatServiceClient(this.pickConn()) return pb.NewTrafficDailyStatServiceClient(this.pickConn())
} }
@@ -603,41 +571,30 @@ func (this *RPCClient) pickConn() *grpc.ClientConn {
defer this.locker.Unlock() defer this.locker.Unlock()
// 检查连接状态 // 检查连接状态
if len(this.conns) > 0 { var countConns = len(this.conns)
var availableConns = []*grpc.ClientConn{} if countConns > 0 {
for _, state := range []connectivity.State{connectivity.Ready, connectivity.Idle, connectivity.Connecting} { 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 { for _, conn := range this.conns {
if conn.GetState() == state { if conn.GetState() == state {
availableConns = append(availableConns, conn) availableConns = append(availableConns, conn)
} }
} }
if len(availableConns) > 0 { 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()
}
} }
// 重新初始化 return this.randConn(this.conns)
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)]
} }
// Close 关闭 // Close 关闭
@@ -671,3 +628,14 @@ func (this *RPCClient) localIPAddrs() []string {
} }
return localIPAddrs 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,9 @@ package numberutils
import ( import (
"fmt" "fmt"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"regexp"
"strconv" "strconv"
"strings"
) )
func FormatInt64(value int64) string { func FormatInt64(value int64) string {
@@ -28,17 +30,35 @@ func FormatBytes(bytes int64) string {
if bytes < Pow1024(1) { if bytes < Pow1024(1) {
return FormatInt64(bytes) + "B" return FormatInt64(bytes) + "B"
} else if bytes < Pow1024(2) { } else if bytes < Pow1024(2) {
return fmt.Sprintf("%.2fKB", float64(bytes)/float64(Pow1024(1))) return TrimZeroSuffix(fmt.Sprintf("%.2fKB", float64(bytes)/float64(Pow1024(1))))
} else if bytes < Pow1024(3) { } else if bytes < Pow1024(3) {
return fmt.Sprintf("%.2fMB", float64(bytes)/float64(Pow1024(2))) return TrimZeroSuffix(fmt.Sprintf("%.2fMB", float64(bytes)/float64(Pow1024(2))))
} else if bytes < Pow1024(4) { } else if bytes < Pow1024(4) {
return fmt.Sprintf("%.2fGB", float64(bytes)/float64(Pow1024(3))) return TrimZeroSuffix(fmt.Sprintf("%.2fGB", float64(bytes)/float64(Pow1024(3))))
} else if bytes < Pow1024(5) { } else if bytes < Pow1024(5) {
return fmt.Sprintf("%.2fTB", float64(bytes)/float64(Pow1024(4))) return TrimZeroSuffix(fmt.Sprintf("%.2fTB", float64(bytes)/float64(Pow1024(4))))
} else if bytes < Pow1024(6) { } else if bytes < Pow1024(6) {
return fmt.Sprintf("%.2fPB", float64(bytes)/float64(Pow1024(5))) return TrimZeroSuffix(fmt.Sprintf("%.2fPB", float64(bytes)/float64(Pow1024(5))))
} else { } else {
return fmt.Sprintf("%.2fEB", float64(bytes)/float64(Pow1024(6))) return TrimZeroSuffix(fmt.Sprintf("%.2fEB", float64(bytes)/float64(Pow1024(6))))
}
}
func FormatBits(bits int64) string {
if bits < Pow1024(1) {
return FormatInt64(bits) + "bps"
} else if bits < Pow1024(2) {
return TrimZeroSuffix(fmt.Sprintf("%.4fKbps", float64(bits)/float64(Pow1024(1))))
} else if bits < Pow1024(3) {
return TrimZeroSuffix(fmt.Sprintf("%.4fMbps", float64(bits)/float64(Pow1024(2))))
} else if bits < Pow1024(4) {
return TrimZeroSuffix(fmt.Sprintf("%.4fGbps", float64(bits)/float64(Pow1024(3))))
} else if bits < Pow1024(5) {
return TrimZeroSuffix(fmt.Sprintf("%.4fTbps", float64(bits)/float64(Pow1024(4))))
} else if bits < Pow1024(6) {
return TrimZeroSuffix(fmt.Sprintf("%.4fPbps", float64(bits)/float64(Pow1024(5))))
} else {
return TrimZeroSuffix(fmt.Sprintf("%.4fEbps", float64(bits)/float64(Pow1024(6))))
} }
} }
@@ -62,9 +82,19 @@ func FormatFloat(f interface{}, decimal int) string {
switch x := f.(type) { switch x := f.(type) {
case float32, float64: case float32, float64:
var s = fmt.Sprintf("%."+types.String(decimal)+"f", x) var s = fmt.Sprintf("%."+types.String(decimal)+"f", x)
// 分隔
var dotIndex = strings.Index(s, ".")
if dotIndex > 0 {
var d = s[:dotIndex]
var f2 = s[dotIndex:]
f2 = strings.TrimRight(strings.TrimRight(f2, "0"), ".")
return formatDigit(d) + f2
}
return s return s
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
return types.String(x) return formatDigit(types.String(x))
case string: case string:
return x return x
} }
@@ -74,3 +104,44 @@ func FormatFloat(f interface{}, decimal int) string {
func FormatFloat2(f interface{}) string { func FormatFloat2(f interface{}) string {
return FormatFloat(f, 2) return FormatFloat(f, 2)
} }
var decimalReg = regexp.MustCompile(`^(\d+\.\d+)([a-zA-Z]+)?$`)
// TrimZeroSuffix 去除小数数字尾部多余的0
func TrimZeroSuffix(s string) string {
var matches = decimalReg.FindStringSubmatch(s)
if len(matches) < 3 {
return s
}
return strings.TrimRight(strings.TrimRight(matches[1], "0"), ".") + matches[2]
}
func formatDigit(d string) string {
if len(d) == 0 {
return d
}
var prefix = ""
if d[0] < '0' || d[0] > '9' {
prefix = d[:1]
d = d[1:]
}
var l = len(d)
if l > 3 {
var pieces = l / 3
var commIndex = l - pieces*3
var d2 = ""
if commIndex > 0 {
d2 = d[:commIndex] + ", "
}
for i := 0; i < pieces; i++ {
d2 += d[commIndex+i*3 : commIndex+i*3+3]
if i != pieces-1 {
d2 += ", "
}
}
return prefix + d2
}
return prefix + d
}

View File

@@ -33,4 +33,35 @@ func TestFormatFloat(t *testing.T) {
t.Log(numberutils.FormatFloat(100.23456, 2)) t.Log(numberutils.FormatFloat(100.23456, 2))
t.Log(numberutils.FormatFloat(100.000023, 2)) t.Log(numberutils.FormatFloat(100.000023, 2))
t.Log(numberutils.FormatFloat(100.012, 2)) t.Log(numberutils.FormatFloat(100.012, 2))
t.Log(numberutils.FormatFloat(123.012, 2))
t.Log(numberutils.FormatFloat(1234.012, 2))
t.Log(numberutils.FormatFloat(12345.012, 2))
t.Log(numberutils.FormatFloat(123456.012, 2))
t.Log(numberutils.FormatFloat(1234567.012, 2))
t.Log(numberutils.FormatFloat(12345678.012, 2))
t.Log(numberutils.FormatFloat(123456789.012, 2))
t.Log(numberutils.FormatFloat(1234567890.012, 2))
t.Log(numberutils.FormatFloat(123, 2))
t.Log(numberutils.FormatFloat(1234, 2))
t.Log(numberutils.FormatFloat(1234.00001, 4))
t.Log(numberutils.FormatFloat(1234.56700, 4))
t.Log(numberutils.FormatFloat(-1234.56700, 2))
t.Log(numberutils.FormatFloat(-221745.12, 2))
}
func TestTrimZeroSuffix(t *testing.T) {
for _, s := range []string{
"1",
"1.0000",
"1.10",
"100",
"100.0000",
"100.0",
"100.0123",
"100.0010",
"100.000KB",
"100.010MB",
} {
t.Log(s, "=>", numberutils.TrimZeroSuffix(s))
}
} }

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) { 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 { func (this *ParentAction) NewPage(total int64, size ...int64) *Page {
@@ -126,11 +130,8 @@ func (this *ParentAction) RPC() *rpc.RPCClient {
} }
// AdminContext 获取Context // AdminContext 获取Context
// 每个请求的context都必须是一个新的实例
func (this *ParentAction) AdminContext() context.Context { func (this *ParentAction) AdminContext() context.Context {
if this.ctx != nil {
return this.ctx
}
if this.rpcClient == nil { if this.rpcClient == nil {
rpcClient, err := rpc.SharedRPC() rpcClient, err := rpc.SharedRPC()
if err != nil { if err != nil {

View File

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

View File

@@ -4,10 +4,12 @@ import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs" "github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "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/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
) )
type CreatePopupAction struct { type CreatePopupAction struct {
@@ -40,11 +42,11 @@ func (this *CreatePopupAction) RunPost(params struct {
Field("name", params.Name). Field("name", params.Name).
Require("请输入API节点名称") Require("请输入API节点名称")
httpConfig := &serverconfigs.HTTPProtocolConfig{} var httpConfig = &serverconfigs.HTTPProtocolConfig{}
httpsConfig := &serverconfigs.HTTPSProtocolConfig{} var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
// 监听地址 // 监听地址
listens := []*serverconfigs.NetworkAddressConfig{} var listens = []*serverconfigs.NetworkAddressConfig{}
err := json.Unmarshal(params.ListensJSON, &listens) err := json.Unmarshal(params.ListensJSON, &listens)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
@@ -64,15 +66,19 @@ func (this *CreatePopupAction) RunPost(params struct {
} }
// Rest监听地址 // Rest监听地址
restHTTPConfig := &serverconfigs.HTTPProtocolConfig{} var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
restHTTPSConfig := &serverconfigs.HTTPSProtocolConfig{} var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{}
if params.RestIsOn { if params.RestIsOn {
restListens := []*serverconfigs.NetworkAddressConfig{} var restListens = []*serverconfigs.NetworkAddressConfig{}
err = json.Unmarshal(params.RestListensJSON, &restListens) err = json.Unmarshal(params.RestListensJSON, &restListens)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
if len(restListens) == 0 {
this.Fail("请至少添加一个HTTP API监听端口")
return
}
for _, addr := range restListens { for _, addr := range restListens {
if addr.Protocol.IsHTTPFamily() { if addr.Protocol.IsHTTPFamily() {
restHTTPConfig.IsOn = true restHTTPConfig.IsOn = true
@@ -82,10 +88,35 @@ func (this *CreatePopupAction) RunPost(params struct {
restHTTPSConfig.Listen = append(restHTTPSConfig.Listen, addr) 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 { if len(params.CertIdsJSON) > 0 {
err = json.Unmarshal(params.CertIdsJSON, &certIds) err = json.Unmarshal(params.CertIdsJSON, &certIds)
if err != nil { if err != nil {
@@ -97,7 +128,7 @@ func (this *CreatePopupAction) RunPost(params struct {
this.Fail("请添加至少一个证书") this.Fail("请添加至少一个证书")
} }
certRefs := []*sslconfigs.SSLCertRef{} var certRefs = []*sslconfigs.SSLCertRef{}
for _, certId := range certIds { for _, certId := range certIds {
certRefs = append(certRefs, &sslconfigs.SSLCertRef{ certRefs = append(certRefs, &sslconfigs.SSLCertRef{
IsOn: true, 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) err = json.Unmarshal(params.AccessAddrsJSON, &accessAddrs)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -4,10 +4,12 @@ import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs" "github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "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/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
) )
@@ -175,12 +177,16 @@ func (this *UpdateAction) RunPost(params struct {
var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{} var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{} var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{}
if params.RestIsOn { if params.RestIsOn {
restListens := []*serverconfigs.NetworkAddressConfig{} var restListens = []*serverconfigs.NetworkAddressConfig{}
err = json.Unmarshal(params.RestListensJSON, &restListens) err = json.Unmarshal(params.RestListensJSON, &restListens)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
if len(restListens) == 0 {
this.Fail("请至少添加一个HTTP API监听端口")
return
}
for _, addr := range restListens { for _, addr := range restListens {
if addr.Protocol.IsHTTPFamily() { if addr.Protocol.IsHTTPFamily() {
restHTTPConfig.IsOn = true restHTTPConfig.IsOn = true
@@ -190,6 +196,31 @@ func (this *UpdateAction) RunPost(params struct {
restHTTPSConfig.Listen = append(restHTTPSConfig.Listen, addr) 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/EdgeAdmin/internal/web/actions/default/nodes/ipAddresses/ipaddressutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"time" "time"
@@ -300,6 +301,22 @@ func (this *DetailAction) RunGet(params struct {
lnAddrs = []string{} 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{ this.Data["node"] = maps.Map{
"id": node.Id, "id": node.Id,
"name": node.Name, "name": node.Name,
@@ -318,6 +335,8 @@ func (this *DetailAction) RunGet(params struct {
"level": node.Level, "level": node.Level,
"levelInfo": nodeconfigs.FindNodeLevel(int(node.Level)), "levelInfo": nodeconfigs.FindNodeLevel(int(node.Level)),
"lnAddrs": lnAddrs, "lnAddrs": lnAddrs,
"enableIPLists": node.EnableIPLists,
"apiNodeAddrs": apiNodeAddrStrings,
"status": maps.Map{ "status": maps.Map{
"isActive": status.IsActive, "isActive": status.IsActive,
@@ -337,6 +356,8 @@ func (this *DetailAction) RunGet(params struct {
"cacheTotalDiskSize": numberutils.FormatBytes(status.CacheTotalDiskSize), "cacheTotalDiskSize": numberutils.FormatBytes(status.CacheTotalDiskSize),
"cacheTotalMemorySize": numberutils.FormatBytes(status.CacheTotalMemorySize), "cacheTotalMemorySize": numberutils.FormatBytes(status.CacheTotalMemorySize),
"exePath": status.ExePath, "exePath": status.ExePath,
"apiSuccessPercent": status.APISuccessPercent,
"apiAvgCostSeconds": status.APIAvgCostSeconds,
}, },
"group": groupMap, "group": groupMap,

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps" "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) var nodeMap = this.Data["node"].(maps.Map)
nodeMap["maxCacheDiskCapacity"] = maxCacheDiskCapacity nodeMap["maxCacheDiskCapacity"] = maxCacheDiskCapacity
nodeMap["cacheDiskDir"] = node.CacheDiskDir nodeMap["cacheDiskDir"] = node.CacheDiskDir
nodeMap["cacheDiskSubDirs"] = diskSubDirs
nodeMap["maxCacheMemoryCapacity"] = maxCacheMemoryCapacity nodeMap["maxCacheMemoryCapacity"] = maxCacheMemoryCapacity
this.Show() this.Show()
@@ -69,6 +80,7 @@ func (this *IndexAction) RunPost(params struct {
NodeId int64 NodeId int64
MaxCacheDiskCapacityJSON []byte MaxCacheDiskCapacityJSON []byte
CacheDiskDir string CacheDiskDir string
CacheDiskSubDirsJSON []byte
MaxCacheMemoryCapacityJSON []byte MaxCacheMemoryCapacityJSON []byte
Must *actions.Must 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{ _, err := this.RPC().NodeRPC().UpdateNodeCache(this.AdminContext(), &pb.UpdateNodeCacheRequest{
NodeId: params.NodeId, NodeId: params.NodeId,
MaxCacheDiskCapacity: pbMaxCacheDiskCapacity, MaxCacheDiskCapacity: pbMaxCacheDiskCapacity,
CacheDiskDir: params.CacheDiskDir, CacheDiskDir: params.CacheDiskDir,
CacheDiskSubDirsJSON: params.CacheDiskSubDirsJSON,
MaxCacheMemoryCapacity: pbMaxCacheMemoryCapacity, MaxCacheMemoryCapacity: pbMaxCacheMemoryCapacity,
}) })
if err != nil { 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/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
) )
@@ -50,6 +51,22 @@ func (this *IndexAction) RunGet(params struct {
} }
this.Data["dnsResolverConfig"] = dnsResolverConfig 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() this.Show()
} }
@@ -59,6 +76,8 @@ func (this *IndexAction) RunPost(params struct {
DnsResolverJSON []byte DnsResolverJSON []byte
ApiNodeAddrsJSON []byte
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
}) { }) {
@@ -68,6 +87,7 @@ func (this *IndexAction) RunPost(params struct {
this.Fail("CPU线程数不能小于0") this.Fail("CPU线程数不能小于0")
} }
// 系统设置
_, err := this.RPC().NodeRPC().UpdateNodeSystem(this.AdminContext(), &pb.UpdateNodeSystemRequest{ _, err := this.RPC().NodeRPC().UpdateNodeSystem(this.AdminContext(), &pb.UpdateNodeSystemRequest{
NodeId: params.NodeId, NodeId: params.NodeId,
MaxCPU: params.MaxCPU, MaxCPU: params.MaxCPU,
@@ -77,6 +97,7 @@ func (this *IndexAction) RunPost(params struct {
return return
} }
// DNS解析设置
var dnsResolverConfig = nodeconfigs.DefaultDNSResolverConfig() var dnsResolverConfig = nodeconfigs.DefaultDNSResolverConfig()
err = json.Unmarshal(params.DnsResolverJSON, dnsResolverConfig) err = json.Unmarshal(params.DnsResolverJSON, dnsResolverConfig)
if err != nil { if err != nil {
@@ -98,5 +119,28 @@ func (this *IndexAction) RunPost(params struct {
return 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() this.Success()
} }

View File

@@ -100,14 +100,15 @@ func (this *UpdateAction) RunGet(params struct {
} }
var nodeMap = maps.Map{ var nodeMap = maps.Map{
"id": node.Id, "id": node.Id,
"name": node.Name, "name": node.Name,
"ipAddresses": ipAddressMaps, "ipAddresses": ipAddressMaps,
"cluster": clusterMap, "cluster": clusterMap,
"isOn": node.IsOn, "isOn": node.IsOn,
"group": groupMap, "group": groupMap,
"region": regionMap, "region": regionMap,
"level": node.Level, "level": node.Level,
"enableIPLists": node.EnableIPLists,
} }
if node.LnAddrs == nil { if node.LnAddrs == nil {
@@ -157,6 +158,7 @@ func (this *UpdateAction) RunPost(params struct {
IsOn bool IsOn bool
Level int32 Level int32
LnAddrs []string LnAddrs []string
EnableIPLists bool
Must *actions.Must Must *actions.Must
}) { }) {
@@ -234,6 +236,7 @@ func (this *UpdateAction) RunPost(params struct {
IsOn: params.IsOn, IsOn: params.IsOn,
Level: params.Level, Level: params.Level,
LnAddrs: lnAddrs, LnAddrs: lnAddrs,
EnableIPLists: params.EnableIPLists,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -78,6 +78,11 @@ func (this *IndexAction) RunPost(params struct {
HttpAllAllowNodeIP bool HttpAllAllowNodeIP bool
HttpAllDefaultDomain string HttpAllDefaultDomain string
HttpAccessLogEnableRequestHeaders bool
HttpAccessLogEnableResponseHeaders bool
HttpAccessLogCommonRequestHeadersOnly bool
HttpAccessLogEnableCookies bool
LogRecordServerError bool LogRecordServerError bool
Must *actions.Must Must *actions.Must
@@ -123,6 +128,11 @@ func (this *IndexAction) RunPost(params struct {
config.HTTPAll.AllowNodeIP = params.HttpAllAllowNodeIP config.HTTPAll.AllowNodeIP = params.HttpAllAllowNodeIP
config.HTTPAll.DefaultDomain = params.HttpAllDefaultDomain config.HTTPAll.DefaultDomain = params.HttpAllDefaultDomain
config.HTTPAccessLog.EnableRequestHeaders = params.HttpAccessLogEnableRequestHeaders
config.HTTPAccessLog.EnableResponseHeaders = params.HttpAccessLogEnableResponseHeaders
config.HTTPAccessLog.CommonRequestHeadersOnly = params.HttpAccessLogCommonRequestHeadersOnly
config.HTTPAccessLog.EnableCookies = params.HttpAccessLogEnableCookies
config.Log.RecordServerError = params.LogRecordServerError config.Log.RecordServerError = params.LogRecordServerError
err = config.Init() err = config.Init()

View File

@@ -45,7 +45,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
grant := grantResp.NodeGrant var grant = grantResp.NodeGrant
if grant != nil { if grant != nil {
grantMap = maps.Map{ grantMap = maps.Map{
"id": grant.Id, "id": grant.Id,
@@ -79,6 +79,16 @@ func (this *IndexAction) RunGet(params struct {
} }
} }
// SSH参数
var sshParams = nodeconfigs.DefaultSSHParams()
if len(cluster.SshParamsJSON) > 0 {
err = json.Unmarshal(cluster.SshParamsJSON, sshParams)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["cluster"] = maps.Map{ this.Data["cluster"] = maps.Map{
"id": cluster.Id, "id": cluster.Id,
"name": cluster.Name, "name": cluster.Name,
@@ -89,6 +99,7 @@ func (this *IndexAction) RunGet(params struct {
"clock": clockConfig, "clock": clockConfig,
"autoRemoteStart": cluster.AutoRemoteStart, "autoRemoteStart": cluster.AutoRemoteStart,
"autoInstallNftables": cluster.AutoInstallNftables, "autoInstallNftables": cluster.AutoInstallNftables,
"sshParams": sshParams,
} }
// 默认值 // 默认值
@@ -104,12 +115,14 @@ func (this *IndexAction) RunPost(params struct {
ClusterId int64 ClusterId int64
Name string Name string
GrantId int64 GrantId int64
SshParamsPort int
InstallDir string InstallDir string
TimeZone string TimeZone string
NodeMaxThreads int32 NodeMaxThreads int32
AutoOpenPorts bool AutoOpenPorts bool
ClockAutoSync bool ClockAutoSync bool
ClockServer string ClockServer string
ClockCheckChrony bool
AutoRemoteStart bool AutoRemoteStart bool
AutoInstallNftables bool AutoInstallNftables bool
@@ -129,9 +142,20 @@ func (this *IndexAction) RunPost(params struct {
Lte(int64(nodeconfigs.DefaultMaxThreadsMax), "单节点最大线程数最大值不能大于"+types.String(nodeconfigs.DefaultMaxThreadsMax)) Lte(int64(nodeconfigs.DefaultMaxThreadsMax), "单节点最大线程数最大值不能大于"+types.String(nodeconfigs.DefaultMaxThreadsMax))
} }
// ssh
var sshParams = nodeconfigs.DefaultSSHParams()
sshParams.Port = params.SshParamsPort
sshParamsJSON, err := json.Marshal(sshParams)
if err != nil {
this.ErrorPage(err)
return
}
// clock
var clockConfig = nodeconfigs.DefaultClockConfig() var clockConfig = nodeconfigs.DefaultClockConfig()
clockConfig.AutoSync = params.ClockAutoSync clockConfig.AutoSync = params.ClockAutoSync
clockConfig.Server = params.ClockServer clockConfig.Server = params.ClockServer
clockConfig.CheckChrony = params.ClockCheckChrony
clockConfigJSON, err := json.Marshal(clockConfig) clockConfigJSON, err := json.Marshal(clockConfig)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
@@ -155,6 +179,7 @@ func (this *IndexAction) RunPost(params struct {
ClockJSON: clockConfigJSON, ClockJSON: clockConfigJSON,
AutoRemoteStart: params.AutoRemoteStart, AutoRemoteStart: params.AutoRemoteStart,
AutoInstallNftables: params.AutoInstallNftables, AutoInstallNftables: params.AutoInstallNftables,
SshParamsJSON: sshParamsJSON,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -60,7 +60,7 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
var tabbar = actionutils.NewTabbar() var tabbar = actionutils.NewTabbar()
tabbar.Add("集群列表", "", "/clusters", "", false) tabbar.Add("集群列表", "", "/clusters", "", false)
if teaconst.IsPlus { 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/nodes?clusterId="+clusterIdString, "server", selectedTabbar == "node")
tabbar.Add("集群设置", "", "/clusters/cluster/settings?clusterId="+clusterIdString, "setting", selectedTabbar == "setting") tabbar.Add("集群设置", "", "/clusters/cluster/settings?clusterId="+clusterIdString, "setting", selectedTabbar == "setting")

View File

@@ -11,7 +11,7 @@ type IndexAction struct {
} }
func (this *IndexAction) Init() { func (this *IndexAction) Init() {
this.Nav("", "", "region") this.Nav("", "", "index")
} }
func (this *IndexAction) RunGet(params struct{}) { func (this *IndexAction) RunGet(params struct{}) {
@@ -20,7 +20,7 @@ func (this *IndexAction) RunGet(params struct{}) {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
regionMaps := []maps.Map{} var regionMaps = []maps.Map{}
for _, region := range regionsResp.NodeRegions { for _, region := range regionsResp.NodeRegions {
countNodesResp, err := this.RPC().NodeRPC().CountAllEnabledNodesWithNodeRegionId(this.AdminContext(), &pb.CountAllEnabledNodesWithNodeRegionIdRequest{NodeRegionId: region.Id}) countNodesResp, err := this.RPC().NodeRPC().CountAllEnabledNodesWithNodeRegionId(this.AdminContext(), &pb.CountAllEnabledNodesWithNodeRegionIdRequest{NodeRegionId: region.Id})
if err != nil { if err != nil {

View File

@@ -18,9 +18,13 @@ func init() {
GetPost("/updatePopup", new(UpdatePopupAction)). GetPost("/updatePopup", new(UpdatePopupAction)).
Post("/delete", new(DeleteAction)). Post("/delete", new(DeleteAction)).
Post("/sort", new(SortAction)). Post("/sort", new(SortAction)).
Get("/nodes", new(NodesAction)).
GetPost("/updateNodeRegionPopup", new(UpdateNodeRegionPopupAction)).
//
GetPost("/selectPopup", new(SelectPopupAction)). GetPost("/selectPopup", new(SelectPopupAction)).
GetPost("/prices", new(PricesAction)).
GetPost("/updatePricePopup", new(UpdatePricePopupAction)). //
EndAll() EndAll()
}) })
} }

View File

@@ -1,50 +0,0 @@
package items
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/regions/regionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
BitsFrom int64
BitsTo int64
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入名称").
Field("bitsFrom", params.BitsFrom).
Gte(0, "请输入不小于0的整数").
Field("bitsTo", params.BitsTo).
Gte(0, "请输入不小于0的整数")
createResp, err := this.RPC().NodePriceItemRPC().CreateNodePriceItem(this.AdminContext(), &pb.CreateNodePriceItemRequest{
Name: params.Name,
Type: regionutils.PriceTypeTraffic,
BitsFrom: params.BitsFrom * 1000 * 1000,
BitsTo: params.BitsTo * 1000 * 1000,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo("创建流量价格项目", createResp.NodePriceItemId)
this.Success()
}

View File

@@ -1,24 +0,0 @@
package items
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
ItemId int64
}) {
defer this.CreateLogInfo("删除流量价格项目 %d", params.ItemId)
_, err := this.RPC().NodePriceItemRPC().DeleteNodePriceItem(this.AdminContext(), &pb.DeleteNodePriceItemRequest{NodePriceItemId: params.ItemId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,21 +0,0 @@
package items
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNode)).
Data("teaMenu", "clusters").
Data("teaSubMenu", "region").
Prefix("/clusters/regions/items").
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/updatePopup", new(UpdatePopupAction)).
Post("/delete", new(DeleteAction)).
EndAll()
})
}

View File

@@ -1,73 +0,0 @@
package items
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
ItemId int64
}) {
itemResp, err := this.RPC().NodePriceItemRPC().FindEnabledNodePriceItem(this.AdminContext(), &pb.FindEnabledNodePriceItemRequest{NodePriceItemId: params.ItemId})
if err != nil {
this.ErrorPage(err)
return
}
item := itemResp.NodePriceItem
if item == nil {
this.NotFound("nodePriceItem", params.ItemId)
return
}
this.Data["item"] = maps.Map{
"id": item.Id,
"name": item.Name,
"bitsFrom": item.BitsFrom,
"bitsTo": item.BitsTo,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
ItemId int64
Name string
BitsFrom int64
BitsTo int64
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("修改流量价格项目 %d", params.ItemId)
params.Must.
Field("name", params.Name).
Require("请输入名称").
Field("bitsFrom", params.BitsFrom).
Gte(0, "请输入不小于0的整数").
Field("bitsTo", params.BitsTo).
Gte(0, "请输入不小于0的整数")
_, err := this.RPC().NodePriceItemRPC().UpdateNodePriceItem(this.AdminContext(), &pb.UpdateNodePriceItemRequest{
NodePriceItemId: params.ItemId,
Name: params.Name,
BitsFrom: params.BitsFrom * 1000 * 1000,
BitsTo: params.BitsTo * 1000 * 1000,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,92 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package regions
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type NodesAction struct {
actionutils.ParentAction
}
func (this *NodesAction) Init() {
this.Nav("", "", "node")
}
func (this *NodesAction) RunGet(params struct {
RegionId int64
}) {
this.Data["regionId"] = params.RegionId
// 所有区域
regionsResp, err := this.RPC().NodeRegionRPC().FindAllAvailableNodeRegions(this.AdminContext(), &pb.FindAllAvailableNodeRegionsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var regionMaps = []maps.Map{}
for _, region := range regionsResp.NodeRegions {
regionMaps = append(regionMaps, maps.Map{
"id": region.Id,
"name": region.Name,
})
}
this.Data["regions"] = regionMaps
// 节点数量
countResp, err := this.RPC().NodeRPC().CountAllNodeRegionInfo(this.AdminContext(), &pb.CountAllNodeRegionInfoRequest{NodeRegionId: params.RegionId})
if err != nil {
this.ErrorPage(err)
return
}
var page = this.NewPage(countResp.Count)
this.Data["page"] = page.AsHTML()
// 节点列表
var hasNodesWithoutRegion = false
nodesResp, err := this.RPC().NodeRPC().ListNodeRegionInfo(this.AdminContext(), &pb.ListNodeRegionInfoRequest{
NodeRegionId: params.RegionId,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var nodeMaps = []maps.Map{}
for _, node := range nodesResp.InfoList {
// region
var regionMap maps.Map
if node.NodeRegion != nil {
regionMap = maps.Map{
"id": node.NodeRegion.Id,
"name": node.NodeRegion.Name,
}
} else {
hasNodesWithoutRegion = true
}
// cluster
var clusterMap maps.Map
if node.NodeCluster != nil {
clusterMap = maps.Map{
"id": node.NodeCluster.Id,
"name": node.NodeCluster.Name,
}
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"name": node.Name,
"region": regionMap,
"cluster": clusterMap,
})
}
this.Data["nodes"] = nodeMaps
this.Data["hasNodesWithoutRegion"] = hasNodesWithoutRegion
this.Show()
}

View File

@@ -1,86 +0,0 @@
package regions
import (
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/regions/regionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type PricesAction struct {
actionutils.ParentAction
}
func (this *PricesAction) Init() {
this.Nav("", "", "price")
}
func (this *PricesAction) RunGet(params struct{}) {
// 所有价格项目
itemsResp, err := this.RPC().NodePriceItemRPC().FindAllAvailableNodePriceItems(this.AdminContext(), &pb.FindAllAvailableNodePriceItemsRequest{Type: regionutils.PriceTypeTraffic})
if err != nil {
this.ErrorPage(err)
return
}
itemMaps := []maps.Map{}
for _, item := range itemsResp.NodePriceItems {
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"name": item.Name,
"bitsFromString": this.formatBits(item.BitsFrom),
"bitsToString": this.formatBits(item.BitsTo),
})
}
this.Data["items"] = itemMaps
// 所有区域
regionsResp, err := this.RPC().NodeRegionRPC().FindAllEnabledNodeRegions(this.AdminContext(), &pb.FindAllEnabledNodeRegionsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
regionMaps := []maps.Map{}
for _, region := range regionsResp.NodeRegions {
pricesMap := map[string]float32{}
if len(region.PricesJSON) > 0 {
err = json.Unmarshal(region.PricesJSON, &pricesMap)
if err != nil {
this.ErrorPage(err)
return
}
}
regionMaps = append(regionMaps, maps.Map{
"id": region.Id,
"isOn": region.IsOn,
"name": region.Name,
"prices": pricesMap,
})
}
this.Data["regions"] = regionMaps
this.Show()
}
func (this *PricesAction) formatBits(bits int64) string {
sizeHuman := ""
if bits < 1000 {
sizeHuman = numberutils.FormatInt64(bits) + "BPS"
} else if bits < 1_000_000 {
sizeHuman = fmt.Sprintf("%.2fKBPS", float64(bits)/1000)
} else if bits < 1_000_000_000 {
sizeHuman = fmt.Sprintf("%.2fMBPS", float64(bits)/1000/1000)
} else if bits < 1_000_000_000_000 {
sizeHuman = fmt.Sprintf("%.2fGBPS", float64(bits)/1000/1000/1000)
} else if bits < 1_000_000_000_000_000 {
sizeHuman = fmt.Sprintf("%.2fTBPS", float64(bits)/1000/1000/1000/1000)
} else if bits < 1_000_000_000_000_000_000 {
sizeHuman = fmt.Sprintf("%.2fPBPS", float64(bits)/1000/1000/1000/1000/1000)
} else {
sizeHuman = fmt.Sprintf("%.2fEBPS", float64(bits)/1000/1000/1000/1000/1000/1000)
}
return sizeHuman
}

View File

@@ -1,5 +0,0 @@
package regionutils
const (
PriceTypeTraffic = "traffic"
)

View File

@@ -40,12 +40,18 @@ func (this *SelectPopupAction) RunPost(params struct {
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
}) { }) {
if params.RegionId <= 0 {
this.Data["region"] = nil
this.Success()
return
}
regionResp, err := this.RPC().NodeRegionRPC().FindEnabledNodeRegion(this.AdminContext(), &pb.FindEnabledNodeRegionRequest{NodeRegionId: params.RegionId}) regionResp, err := this.RPC().NodeRegionRPC().FindEnabledNodeRegion(this.AdminContext(), &pb.FindEnabledNodeRegionRequest{NodeRegionId: params.RegionId})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
region := regionResp.NodeRegion var region = regionResp.NodeRegion
if region == nil { if region == nil {
this.NotFound("nodeRegion", params.RegionId) this.NotFound("nodeRegion", params.RegionId)
return return

View File

@@ -0,0 +1,97 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package regions
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdateNodeRegionPopupAction struct {
actionutils.ParentAction
}
func (this *UpdateNodeRegionPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdateNodeRegionPopupAction) RunGet(params struct {
NodeId int64
RegionId int64
}) {
// node
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var node = nodeResp.Node
if node == nil {
this.NotFound("node", params.NodeId)
return
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
}
// region
this.Data["region"] = maps.Map{
"id": 0,
"name": "",
}
if params.RegionId > 0 {
regionResp, err := this.RPC().NodeRegionRPC().FindEnabledNodeRegion(this.AdminContext(), &pb.FindEnabledNodeRegionRequest{NodeRegionId: params.RegionId})
if err != nil {
this.ErrorPage(err)
return
}
var region = regionResp.NodeRegion
if region != nil {
this.Data["region"] = maps.Map{
"id": region.Id,
"name": region.Name,
}
}
}
// all regions
regionsResp, err := this.RPC().NodeRegionRPC().FindAllAvailableNodeRegions(this.AdminContext(), &pb.FindAllAvailableNodeRegionsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var regionMaps = []maps.Map{}
for _, region := range regionsResp.NodeRegions {
regionMaps = append(regionMaps, maps.Map{
"id": region.Id,
"name": region.Name,
})
}
this.Data["regions"] = regionMaps
this.Show()
}
func (this *UpdateNodeRegionPopupAction) RunPost(params struct {
NodeId int64
RegionId int64
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("修改节点 %d 区域到 %d", params.RegionId)
_, err := this.RPC().NodeRPC().UpdateNodeRegionInfo(this.AdminContext(), &pb.UpdateNodeRegionInfoRequest{
NodeId: params.NodeId,
NodeRegionId: params.RegionId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,94 +0,0 @@
package regions
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePricePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePricePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePricePopupAction) RunGet(params struct {
RegionId int64
ItemId int64
}) {
// 区域
regionResp, err := this.RPC().NodeRegionRPC().FindEnabledNodeRegion(this.AdminContext(), &pb.FindEnabledNodeRegionRequest{NodeRegionId: params.RegionId})
if err != nil {
this.ErrorPage(err)
return
}
region := regionResp.NodeRegion
if region == nil {
this.NotFound("nodeRegion", params.RegionId)
return
}
this.Data["region"] = maps.Map{
"id": region.Id,
"isOn": region.IsOn,
"name": region.Name,
}
// 当前价格
pricesMap := map[string]float32{}
if len(region.PricesJSON) > 0 {
err = json.Unmarshal(region.PricesJSON, &pricesMap)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["price"] = pricesMap[numberutils.FormatInt64(params.ItemId)]
// 价格项
itemResp, err := this.RPC().NodePriceItemRPC().FindEnabledNodePriceItem(this.AdminContext(), &pb.FindEnabledNodePriceItemRequest{NodePriceItemId: params.ItemId})
if err != nil {
this.ErrorPage(err)
return
}
item := itemResp.NodePriceItem
if item == nil {
this.NotFound("nodePriceItem", params.ItemId)
return
}
this.Data["item"] = maps.Map{
"id": item.Id,
"name": item.Name,
"bitsFrom": item.BitsFrom,
"bitsTo": item.BitsTo,
}
this.Show()
}
func (this *UpdatePricePopupAction) RunPost(params struct {
RegionId int64
ItemId int64
Price float32
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("修改区域 %d-价格项 %d 的价格", params.RegionId, params.ItemId)
_, err := this.RPC().NodeRegionRPC().UpdateNodeRegionPrice(this.AdminContext(), &pb.UpdateNodeRegionPriceRequest{
NodeRegionId: params.RegionId,
NodeItemId: params.ItemId,
Price: params.Price,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -76,16 +76,14 @@ func (this *IndexAction) RunPost(params struct{}) {
this.Data["dashboard"] = maps.Map{ this.Data["dashboard"] = maps.Map{
"defaultClusterId": resp.DefaultNodeClusterId, "defaultClusterId": resp.DefaultNodeClusterId,
"countServers": resp.CountServers, "countServers": resp.CountServers,
"countNodeClusters": resp.CountNodeClusters, "countNodeClusters": resp.CountNodeClusters,
"countNodes": resp.CountNodes, "countNodes": resp.CountNodes,
"countOfflineNodes": resp.CountOfflineNodes, "countOfflineNodes": resp.CountOfflineNodes,
"countUsers": resp.CountUsers, "countUsers": resp.CountUsers,
"countAPINodes": resp.CountAPINodes, "countAPINodes": resp.CountAPINodes,
"countOfflineAPINodes": resp.CountOfflineAPINodes, "countOfflineAPINodes": resp.CountOfflineAPINodes,
"countDBNodes": resp.CountDBNodes, "countDBNodes": resp.CountDBNodes,
"countUserNodes": resp.CountUserNodes,
"countOfflineUserNodes": resp.CountOfflineUserNodes,
"canGoServers": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeServer), "canGoServers": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeServer),
"canGoNodes": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeNode), "canGoNodes": configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeNode),

View File

@@ -43,7 +43,7 @@ func (this *ClusterAction) RunGet(params struct {
} }
var defaultRoute = dnsResp.DefaultRoute var defaultRoute = dnsResp.DefaultRoute
domainName := "" domainName := ""
dnsMap := maps.Map{ var dnsMap = maps.Map{
"dnsName": dnsResp.Name, "dnsName": dnsResp.Name,
"domainId": 0, "domainId": 0,
"domainName": "", "domainName": "",
@@ -76,14 +76,14 @@ func (this *ClusterAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
nodeMaps := []maps.Map{} var nodeMaps = []maps.Map{}
for _, node := range nodesResp.Nodes { for _, node := range nodesResp.Nodes {
if len(node.Routes) > 0 { if len(node.Routes) > 0 {
for _, route := range node.Routes { for _, route := range node.Routes {
// 检查是否已解析 // 检查是否已解析
isResolved := false var isResolved = false
if cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(node.IpAddr) > 0 { if cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(node.IpAddr) > 0 {
recordType := "A" var recordType = "A"
if utils.IsIPv6(node.IpAddr) { if utils.IsIPv6(node.IpAddr) {
recordType = "AAAA" recordType = "AAAA"
} }
@@ -102,9 +102,10 @@ func (this *ClusterAction) RunGet(params struct {
} }
nodeMaps = append(nodeMaps, maps.Map{ nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id, "id": node.Id,
"name": node.Name, "name": node.Name,
"ipAddr": node.IpAddr, "ipAddr": node.IpAddr,
"ipAddrId": node.NodeIPAddressId,
"route": maps.Map{ "route": maps.Map{
"name": route.Name, "name": route.Name,
"code": route.Code, "code": route.Code,
@@ -117,7 +118,7 @@ func (this *ClusterAction) RunGet(params struct {
// 默认线路 // 默认线路
var isResolved = false var isResolved = false
if len(defaultRoute) > 0 { if len(defaultRoute) > 0 {
recordType := "A" var recordType = "A"
if utils.IsIPv6(node.IpAddr) { if utils.IsIPv6(node.IpAddr) {
recordType = "AAAA" recordType = "AAAA"
} }
@@ -135,9 +136,10 @@ func (this *ClusterAction) RunGet(params struct {
isResolved = checkResp.IsOk isResolved = checkResp.IsOk
} }
nodeMaps = append(nodeMaps, maps.Map{ nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id, "id": node.Id,
"name": node.Name, "name": node.Name,
"ipAddr": node.IpAddr, "ipAddr": node.IpAddr,
"ipAddrId": node.NodeIPAddressId,
"route": maps.Map{ "route": maps.Map{
"name": "", "name": "",
"code": "", "code": "",
@@ -155,7 +157,7 @@ func (this *ClusterAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
serverMaps := []maps.Map{} var serverMaps = []maps.Map{}
for _, server := range serversResp.Servers { for _, server := range serversResp.Servers {
// 检查是否已解析 // 检查是否已解析
isResolved := false isResolved := false
@@ -198,7 +200,7 @@ func (this *ClusterAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
issueMaps := []maps.Map{} var issueMaps = []maps.Map{}
for _, issue := range issuesResp.Issues { for _, issue := range issuesResp.Issues {
issueMaps = append(issueMaps, maps.Map{ issueMaps = append(issueMaps, maps.Map{
"target": issue.Target, "target": issue.Target,
@@ -218,7 +220,7 @@ func (this *ClusterAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
taskMaps := []maps.Map{} var taskMaps = []maps.Map{}
for _, task := range resp.DnsTasks { for _, task := range resp.DnsTasks {
var clusterMap maps.Map = nil var clusterMap maps.Map = nil
var nodeMap maps.Map = nil var nodeMap maps.Map = nil

View File

@@ -71,7 +71,10 @@ func ValidateRecordName(name string) bool {
} }
pieces := strings.Split(name, ".") pieces := strings.Split(name, ".")
for _, piece := range pieces { for index, piece := range pieces {
if index == 0 && piece == "*" {
continue
}
if piece == "-" || if piece == "-" ||
strings.HasPrefix(piece, "-") || strings.HasPrefix(piece, "-") ||
strings.HasSuffix(piece, "-") || strings.HasSuffix(piece, "-") ||

View File

@@ -22,12 +22,14 @@ func (this *UpdateNodePopupAction) Init() {
func (this *UpdateNodePopupAction) RunGet(params struct { func (this *UpdateNodePopupAction) RunGet(params struct {
ClusterId int64 ClusterId int64
NodeId int64 NodeId int64
IpAddrId int64
}) { }) {
this.Data["nodeId"] = params.NodeId this.Data["nodeId"] = params.NodeId
dnsInfoResp, err := this.RPC().NodeRPC().FindEnabledNodeDNS(this.AdminContext(), &pb.FindEnabledNodeDNSRequest{ dnsInfoResp, err := this.RPC().NodeRPC().FindEnabledNodeDNS(this.AdminContext(), &pb.FindEnabledNodeDNSRequest{
NodeId: params.NodeId, NodeId: params.NodeId,
NodeClusterId: params.ClusterId, NodeClusterId: params.ClusterId,
NodeIPAddrId: params.IpAddrId,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
@@ -39,12 +41,13 @@ func (this *UpdateNodePopupAction) RunGet(params struct {
return return
} }
this.Data["ipAddr"] = dnsInfo.IpAddr this.Data["ipAddr"] = dnsInfo.IpAddr
this.Data["ipAddrId"] = dnsInfo.NodeIPAddressId
this.Data["routes"] = domainutils.ConvertRoutesToMaps(dnsInfo) this.Data["routes"] = domainutils.ConvertRoutesToMaps(dnsInfo)
this.Data["domainId"] = dnsInfo.DnsDomainId this.Data["domainId"] = dnsInfo.DnsDomainId
this.Data["domainName"] = dnsInfo.DnsDomainName this.Data["domainName"] = dnsInfo.DnsDomainName
// 读取所有线路 // 读取所有线路
allRouteMaps := []maps.Map{} var allRouteMaps = []maps.Map{}
if dnsInfo.DnsDomainId > 0 { if dnsInfo.DnsDomainId > 0 {
routesResp, err := this.RPC().DNSDomainRPC().FindAllDNSDomainRoutes(this.AdminContext(), &pb.FindAllDNSDomainRoutesRequest{DnsDomainId: dnsInfo.DnsDomainId}) routesResp, err := this.RPC().DNSDomainRPC().FindAllDNSDomainRoutes(this.AdminContext(), &pb.FindAllDNSDomainRoutesRequest{DnsDomainId: dnsInfo.DnsDomainId})
if err != nil { if err != nil {
@@ -75,6 +78,7 @@ func (this *UpdateNodePopupAction) RunGet(params struct {
func (this *UpdateNodePopupAction) RunPost(params struct { func (this *UpdateNodePopupAction) RunPost(params struct {
NodeId int64 NodeId int64
IpAddr string IpAddr string
IpAddrId int64
DomainId int64 DomainId int64
DnsRoutesJSON []byte DnsRoutesJSON []byte
@@ -84,7 +88,7 @@ func (this *UpdateNodePopupAction) RunPost(params struct {
// 操作日志 // 操作日志
defer this.CreateLog(oplogs.LevelInfo, "修改节点 %d 的DNS设置", params.NodeId) defer this.CreateLog(oplogs.LevelInfo, "修改节点 %d 的DNS设置", params.NodeId)
routes := []string{} var routes = []string{}
if len(params.DnsRoutesJSON) > 0 { if len(params.DnsRoutesJSON) > 0 {
err := json.Unmarshal(params.DnsRoutesJSON, &routes) err := json.Unmarshal(params.DnsRoutesJSON, &routes)
if err != nil { if err != nil {
@@ -103,10 +107,11 @@ func (this *UpdateNodePopupAction) RunPost(params struct {
// 执行修改 // 执行修改
_, err := this.RPC().NodeRPC().UpdateNodeDNS(this.AdminContext(), &pb.UpdateNodeDNSRequest{ _, err := this.RPC().NodeRPC().UpdateNodeDNS(this.AdminContext(), &pb.UpdateNodeDNSRequest{
NodeId: params.NodeId, NodeId: params.NodeId,
IpAddr: params.IpAddr, IpAddr: params.IpAddr,
DnsDomainId: params.DomainId, NodeIPAddressId: params.IpAddrId,
Routes: routes, DnsDomainId: params.DomainId,
Routes: routes,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -59,7 +59,7 @@ func (this *IndexAction) RunGet(params struct {
} }
var providerMaps = []maps.Map{} var providerMaps = []maps.Map{}
for _, provider := range providersResp.DnsProviders { for _, provider := range providersResp.DnsProviders {
dataUpdatedTime := "" var dataUpdatedTime = ""
if provider.DataUpdatedAt > 0 { if provider.DataUpdatedAt > 0 {
dataUpdatedTime = timeutil.FormatTime("Y-m-d H:i:s", provider.DataUpdatedAt) dataUpdatedTime = timeutil.FormatTime("Y-m-d H:i:s", provider.DataUpdatedAt)
} }
@@ -72,7 +72,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
countDomains := countDomainsResp.Count var countDomains = countDomainsResp.Count
providerMaps = append(providerMaps, maps.Map{ providerMaps = append(providerMaps, maps.Map{
"id": provider.Id, "id": provider.Id,

View File

@@ -1,6 +1,7 @@
package acme package acme
import ( import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
@@ -25,7 +26,7 @@ func (this *UpdateTaskPopupAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
task := taskResp.AcmeTask var task = taskResp.AcmeTask
if task == nil { if task == nil {
this.NotFound("acmeTask", params.TaskId) this.NotFound("acmeTask", params.TaskId)
return return
@@ -74,7 +75,7 @@ func (this *UpdateTaskPopupAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
providerMaps := []maps.Map{} var providerMaps = []maps.Map{}
for _, provider := range providersResp.DnsProviders { for _, provider := range providersResp.DnsProviders {
providerMaps = append(providerMaps, maps.Map{ providerMaps = append(providerMaps, maps.Map{
"id": provider.Id, "id": provider.Id,
@@ -93,7 +94,7 @@ func (this *UpdateTaskPopupAction) RunPost(params struct {
AcmeUserId int64 AcmeUserId int64
DnsProviderId int64 DnsProviderId int64
DnsDomain string DnsDomain string
Domains []string DomainsJSON []byte
AutoRenew bool AutoRenew bool
AuthURL string AuthURL string
@@ -123,11 +124,20 @@ func (this *UpdateTaskPopupAction) RunPost(params struct {
} }
} }
if len(params.Domains) == 0 { var domains = []string{}
if len(params.DomainsJSON) > 0 {
err := json.Unmarshal(params.DomainsJSON, &domains)
if err != nil {
this.Fail("解析域名数据失败:" + err.Error())
return
}
}
if len(domains) == 0 {
this.Fail("请输入证书域名列表") this.Fail("请输入证书域名列表")
} }
realDomains := []string{} var realDomains = []string{}
for _, domain := range params.Domains { for _, domain := range domains {
domain = strings.ToLower(domain) domain = strings.ToLower(domain)
if params.AuthType == "dns" { if params.AuthType == "dns" {
if !strings.HasSuffix(domain, "."+dnsDomain) && domain != dnsDomain { if !strings.HasSuffix(domain, "."+dnsDomain) && domain != dnsDomain {

View File

@@ -15,7 +15,11 @@ func (this *CreatePopupAction) Init() {
this.Nav("", "", "") this.Nav("", "", "")
} }
func (this *CreatePopupAction) RunGet(params struct{}) { func (this *CreatePopupAction) RunGet(params struct {
ProviderCode string
}) {
this.Data["providerCode"] = params.ProviderCode
// 服务商 // 服务商
providersResp, err := this.RPC().ACMEProviderRPC().FindAllACMEProviders(this.AdminContext(), &pb.FindAllACMEProvidersRequest{}) providersResp, err := this.RPC().ACMEProviderRPC().FindAllACMEProviders(this.AdminContext(), &pb.FindAllACMEProvidersRequest{})
if err != nil { if err != nil {

View File

@@ -30,9 +30,10 @@ func (this *SelectPopupAction) RunGet(params struct {
// TODO 列出常用和最新的证书供用户选择 // TODO 列出常用和最新的证书供用户选择
this.Data["keyword"] = params.Keyword this.Data["keyword"] = params.Keyword
this.Data["selectedCertIds"] = params.SelectedCertIds
// 已经选择的证书 // 已经选择的证书
selectedCertIds := []string{} var selectedCertIds = []string{}
if len(params.SelectedCertIds) > 0 { if len(params.SelectedCertIds) > 0 {
selectedCertIds = strings.Split(params.SelectedCertIds, ",") selectedCertIds = strings.Split(params.SelectedCertIds, ",")
} }

View File

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

View File

@@ -0,0 +1,26 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package headers
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
)
type OptionsAction struct {
actionutils.ParentAction
}
func (this *OptionsAction) RunPost(params struct {
Type string
}) {
if params.Type == "request" {
this.Data["headers"] = serverconfigs.AllHTTPCommonRequestHeaders
} else if params.Type == "response" {
this.Data["headers"] = serverconfigs.AllHTTPCommonResponseHeaders
} else {
this.Data["headers"] = []string{}
}
this.Success()
}

View File

@@ -3,7 +3,6 @@ package servers
import ( import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders" "github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils" "github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
@@ -90,15 +89,16 @@ func (this *IndexAction) RunGet(params struct {
// 服务列表 // 服务列表
serversResp, err := this.RPC().ServerRPC().ListEnabledServersMatch(this.AdminContext(), &pb.ListEnabledServersMatchRequest{ serversResp, err := this.RPC().ServerRPC().ListEnabledServersMatch(this.AdminContext(), &pb.ListEnabledServersMatchRequest{
Offset: page.Offset, Offset: page.Offset,
Size: page.Size, Size: page.Size,
NodeClusterId: params.ClusterId, NodeClusterId: params.ClusterId,
ServerGroupId: params.GroupId, ServerGroupId: params.GroupId,
Keyword: params.Keyword, Keyword: params.Keyword,
AuditingFlag: params.AuditingFlag, AuditingFlag: params.AuditingFlag,
TrafficOutDesc: params.TrafficOutOrder == "desc", TrafficOutDesc: params.TrafficOutOrder == "desc",
TrafficOutAsc: params.TrafficOutOrder == "asc", TrafficOutAsc: params.TrafficOutOrder == "asc",
UserId: params.UserId, UserId: params.UserId,
IgnoreServerNames: true,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
@@ -176,27 +176,35 @@ func (this *IndexAction) RunGet(params struct {
} }
// 域名列表 // 域名列表
var serverNames = []*serverconfigs.ServerNameConfig{}
if server.IsAuditing || (server.AuditingResult != nil && !server.AuditingResult.IsOk) { if server.IsAuditing || (server.AuditingResult != nil && !server.AuditingResult.IsOk) {
server.ServerNamesJSON = server.AuditingServerNamesJSON server.ServerNamesJSON = server.AuditingServerNamesJSON
if len(config.ServerNames) == 0 {
// 审核中的域名
if len(server.ServerNamesJSON) > 0 {
var serverNames = []*serverconfigs.ServerNameConfig{}
err = json.Unmarshal(server.ServerNamesJSON, &serverNames)
if err != nil {
this.ErrorPage(err)
return
}
config.ServerNames = serverNames
}
}
} }
var auditingIsOk = true var auditingIsOk = true
if !server.IsAuditing && server.AuditingResult != nil && !server.AuditingResult.IsOk { if !server.IsAuditing && server.AuditingResult != nil && !server.AuditingResult.IsOk {
auditingIsOk = false auditingIsOk = false
} }
if len(server.ServerNamesJSON) > 0 { var firstServerName = ""
err = json.Unmarshal(server.ServerNamesJSON, &serverNames) for _, serverNameConfig := range config.ServerNames {
if err != nil { if len(serverNameConfig.Name) > 0 {
this.ErrorPage(err) firstServerName = serverNameConfig.Name
return break
} }
} if len(serverNameConfig.SubNames) > 0 {
var countServerNames = 0 firstServerName = serverNameConfig.SubNames[0]
for _, serverName := range serverNames { break
if len(serverName.SubNames) == 0 {
countServerNames++
} else {
countServerNames += len(serverName.SubNames)
} }
} }
@@ -216,9 +224,9 @@ func (this *IndexAction) RunGet(params struct {
} }
// 统计数据 // 统计数据
var bandwidth = "" var bandwidthBits int64 = 0
if server.BandwidthBytes > 0 { if server.BandwidthBytes > 0 {
bandwidth = numberutils.FormatBytes(server.BandwidthBytes) bandwidthBits = server.BandwidthBytes * 8
} }
serverMaps = append(serverMaps, maps.Map{ serverMaps = append(serverMaps, maps.Map{
@@ -232,13 +240,13 @@ func (this *IndexAction) RunGet(params struct {
"ports": portMaps, "ports": portMaps,
"serverTypeName": serverconfigs.FindServerType(server.Type).GetString("name"), "serverTypeName": serverconfigs.FindServerType(server.Type).GetString("name"),
"groups": groupMaps, "groups": groupMaps,
"serverNames": serverNames, "firstServerName": firstServerName,
"countServerNames": countServerNames, "countServerNames": server.CountServerNames,
"isAuditing": server.IsAuditing, "isAuditing": server.IsAuditing,
"auditingIsOk": auditingIsOk, "auditingIsOk": auditingIsOk,
"user": userMap, "user": userMap,
"auditingTime": auditingTime, "auditingTime": auditingTime,
"bandwidth": bandwidth, "bandwidthBits": bandwidthBits,
}) })
} }
this.Data["servers"] = serverMaps this.Data["servers"] = serverMaps

View File

@@ -2,6 +2,7 @@ package servers
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders" "github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/headers"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/users" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/users"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers" "github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo" "github.com/iwind/TeaGo"
@@ -26,10 +27,13 @@ func init() {
Get("/serverNamesPopup", new(ServerNamesPopupAction)). Get("/serverNamesPopup", new(ServerNamesPopupAction)).
Post("/status", new(StatusAction)). Post("/status", new(StatusAction)).
// // user
Post("/users/options", new(users.OptionsAction)). Post("/users/options", new(users.OptionsAction)).
Post("/users/plans", new(users.PlansAction)). Post("/users/plans", new(users.PlansAction)).
// header
Post("/headers/options", new(headers.OptionsAction)).
// //
EndAll() EndAll()
}) })

View File

@@ -21,21 +21,21 @@ func (this *IndexAction) Init() {
} }
func (this *IndexAction) RunGet(params struct { func (this *IndexAction) RunGet(params struct {
Ip string Keyword string
GlobalOnly bool GlobalOnly bool
Unread bool Unread bool
EventLevel string EventLevel string
ListType string ListType string
}) { }) {
this.Data["type"] = "" this.Data["type"] = ""
this.Data["ip"] = params.Ip this.Data["keyword"] = params.Keyword
this.Data["globalOnly"] = params.GlobalOnly this.Data["globalOnly"] = params.GlobalOnly
this.Data["unread"] = params.Unread this.Data["unread"] = params.Unread
this.Data["eventLevel"] = params.EventLevel this.Data["eventLevel"] = params.EventLevel
this.Data["listType"] = params.ListType this.Data["listType"] = params.ListType
countUnreadResp, err := this.RPC().IPItemRPC().CountAllEnabledIPItems(this.AdminContext(), &pb.CountAllEnabledIPItemsRequest{ countUnreadResp, err := this.RPC().IPItemRPC().CountAllEnabledIPItems(this.AdminContext(), &pb.CountAllEnabledIPItemsRequest{
Ip: params.Ip, Keyword: params.Keyword,
GlobalOnly: params.GlobalOnly, GlobalOnly: params.GlobalOnly,
Unread: true, Unread: true,
EventLevel: params.EventLevel, EventLevel: params.EventLevel,
@@ -48,7 +48,7 @@ func (this *IndexAction) RunGet(params struct {
this.Data["countUnread"] = countUnreadResp.Count this.Data["countUnread"] = countUnreadResp.Count
countResp, err := this.RPC().IPItemRPC().CountAllEnabledIPItems(this.AdminContext(), &pb.CountAllEnabledIPItemsRequest{ countResp, err := this.RPC().IPItemRPC().CountAllEnabledIPItems(this.AdminContext(), &pb.CountAllEnabledIPItemsRequest{
Ip: params.Ip, Keyword: params.Keyword,
GlobalOnly: params.GlobalOnly, GlobalOnly: params.GlobalOnly,
Unread: params.Unread, Unread: params.Unread,
EventLevel: params.EventLevel, EventLevel: params.EventLevel,
@@ -63,7 +63,7 @@ func (this *IndexAction) RunGet(params struct {
this.Data["page"] = page.AsHTML() this.Data["page"] = page.AsHTML()
itemsResp, err := this.RPC().IPItemRPC().ListAllEnabledIPItems(this.AdminContext(), &pb.ListAllEnabledIPItemsRequest{ itemsResp, err := this.RPC().IPItemRPC().ListAllEnabledIPItems(this.AdminContext(), &pb.ListAllEnabledIPItemsRequest{
Ip: params.Ip, Keyword: params.Keyword,
GlobalOnly: params.GlobalOnly, GlobalOnly: params.GlobalOnly,
Unread: params.Unread, Unread: params.Unread,
EventLevel: params.EventLevel, EventLevel: params.EventLevel,

View File

@@ -19,8 +19,10 @@ func (this *CreateDeletePopupAction) Init() {
func (this *CreateDeletePopupAction) RunGet(params struct { func (this *CreateDeletePopupAction) RunGet(params struct {
HeaderPolicyId int64 HeaderPolicyId int64
Type string
}) { }) {
this.Data["headerPolicyId"] = params.HeaderPolicyId this.Data["headerPolicyId"] = params.HeaderPolicyId
this.Data["type"] = params.Type
this.Show() this.Show()
} }

View File

@@ -8,7 +8,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
) )
// 删除Header // DeleteAction 删除Header
type DeleteAction struct { type DeleteAction struct {
actionutils.ParentAction actionutils.ParentAction
} }

View File

@@ -65,34 +65,7 @@ func (this *IndexAction) RunGet(params struct {
} }
// 套餐 // 套餐
var userPlanMap = maps.Map{"id": server.UserPlanId, "dayTo": "", "plan": maps.Map{}} this.initUserPlan(server)
if server.UserPlanId > 0 {
userPlanResp, err := this.RPC().UserPlanRPC().FindEnabledUserPlan(this.AdminContext(), &pb.FindEnabledUserPlanRequest{UserPlanId: server.UserPlanId})
if err != nil {
this.ErrorPage(err)
return
}
var userPlan = userPlanResp.UserPlan
if userPlan != nil {
planResp, err := this.RPC().PlanRPC().FindEnabledPlan(this.AdminContext(), &pb.FindEnabledPlanRequest{PlanId: userPlan.PlanId})
if err != nil {
this.ErrorPage(err)
return
}
var plan = planResp.Plan
if plan != nil {
userPlanMap = maps.Map{
"id": userPlan.Id,
"dayTo": userPlan.DayTo,
"plan": maps.Map{
"id": plan.Id,
"name": plan.Name,
},
}
}
}
}
this.Data["userPlan"] = userPlanMap
// 集群 // 集群
clusterId := int64(0) clusterId := int64(0)

View File

@@ -0,0 +1,14 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package settings
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
func (this *IndexAction) initUserPlan(server *pb.Server) {
var userPlanMap = maps.Map{"id": server.UserPlanId, "dayTo": "", "plan": maps.Map{}}
this.Data["userPlan"] = userPlanMap
}

View File

@@ -6,9 +6,9 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"net/url" "net/url"
"regexp" "regexp"
"strings"
) )
type CreatePopupAction struct { type CreatePopupAction struct {
@@ -27,6 +27,9 @@ func (this *CreatePopupAction) RunGet(params struct {
} }
func (this *CreatePopupAction) RunPost(params struct { func (this *CreatePopupAction) RunPost(params struct {
Type string
// URL
Mode string Mode string
BeforeURL string BeforeURL string
AfterURL string AfterURL string
@@ -34,51 +37,131 @@ func (this *CreatePopupAction) RunPost(params struct {
MatchRegexp bool MatchRegexp bool
KeepRequestURI bool KeepRequestURI bool
KeepArgs bool KeepArgs bool
Status int
CondsJSON []byte // 域名
IsOn bool DomainsAll bool
DomainsBeforeJSON []byte
DomainBeforeIgnorePorts bool
DomainAfter string
DomainAfterScheme string
// 端口
PortsAll bool
PortsBefore []string
PortAfter int
PortAfterScheme string
Status int
CondsJSON []byte
IsOn bool
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
}) { }) {
params.Must. var config = &serverconfigs.HTTPHostRedirectConfig{}
Field("beforeURL", params.BeforeURL). config.Type = params.Type
Require("请填写跳转前的URL") config.Status = params.Status
config.IsOn = params.IsOn
switch params.Type {
case serverconfigs.HTTPHostRedirectTypeURL:
params.Must.
Field("beforeURL", params.BeforeURL).
Require("请填写跳转前的URL")
// 校验格式
if params.MatchRegexp {
_, err := regexp.Compile(params.BeforeURL)
if err != nil {
this.Fail("跳转前URL正则表达式错误" + err.Error())
}
} else {
u, err := url.Parse(params.BeforeURL)
if err != nil {
this.FailField("beforeURL", "请输入正确的跳转前URL")
}
if (u.Scheme != "http" && u.Scheme != "https") ||
len(u.Host) == 0 {
this.FailField("beforeURL", "请输入正确的跳转前URL")
}
// 校验格式
if params.MatchRegexp {
_, err := regexp.Compile(params.BeforeURL)
if err != nil {
this.Fail("跳转前URL正则表达式错误" + err.Error())
}
} else {
u, err := url.Parse(params.BeforeURL)
if err != nil {
this.FailField("beforeURL", "请输入正确的跳转前URL")
}
if (u.Scheme != "http" && u.Scheme != "https") ||
len(u.Host) == 0 {
this.FailField("beforeURL", "请输入正确的跳转前URL")
} }
} params.Must.
Field("afterURL", params.AfterURL).
Require("请填写跳转后URL")
params.Must. // 校验格式
Field("afterURL", params.AfterURL). if params.MatchRegexp {
Require("请填写跳转后URL") // 正则表达式情况下不做校验
} else {
u, err := url.Parse(params.AfterURL)
if err != nil {
this.FailField("afterURL", "请输入正确的跳转后URL")
}
if (u.Scheme != "http" && u.Scheme != "https") ||
len(u.Host) == 0 {
this.FailField("afterURL", "请输入正确的跳转后URL")
}
}
// 校验格式 config.Mode = params.Mode
if params.MatchRegexp { config.BeforeURL = params.BeforeURL
// 正则表达式情况下不做校验 config.AfterURL = params.AfterURL
} else { config.MatchPrefix = params.MatchPrefix
u, err := url.Parse(params.AfterURL) config.MatchRegexp = params.MatchRegexp
if err != nil { config.KeepRequestURI = params.KeepRequestURI
this.FailField("afterURL", "请输入正确的跳转后URL") config.KeepArgs = params.KeepArgs
case serverconfigs.HTTPHostRedirectTypeDomain:
config.DomainsAll = params.DomainsAll
var domainsBefore = []string{}
if len(params.DomainsBeforeJSON) > 0 {
err := json.Unmarshal(params.DomainsBeforeJSON, &domainsBefore)
if err != nil {
this.Fail("错误的域名格式:" + err.Error())
return
}
} }
if (u.Scheme != "http" && u.Scheme != "https") || config.DomainsBefore = domainsBefore
len(u.Host) == 0 { if !params.DomainsAll {
this.FailField("afterURL", "请输入正确的跳转后URL") if len(domainsBefore) == 0 {
this.Fail("请输入跳转前域名")
return
}
} }
config.DomainBeforeIgnorePorts = params.DomainBeforeIgnorePorts
if len(params.DomainAfter) == 0 {
this.FailField("domainAfter", "请输入跳转后域名")
return
}
config.DomainAfter = params.DomainAfter
config.DomainAfterScheme = params.DomainAfterScheme
case serverconfigs.HTTPHostRedirectTypePort:
config.PortsAll = params.PortsAll
config.PortsBefore = params.PortsBefore
var portReg = regexp.MustCompile(`^\d+$`)
var portRangeReg = regexp.MustCompile(`^\d+-\d+$`)
if !config.PortsAll {
for _, port := range params.PortsBefore {
port = strings.ReplaceAll(port, " ", "")
if !portReg.MatchString(port) && !portRangeReg.MatchString(port) {
this.Fail("端口号" + port + "填写错误(请输入单个端口号或一个端口范围)")
return
}
}
if len(params.PortsBefore) == 0 {
this.Fail("请输入跳转前端口")
return
}
}
if params.PortAfter <= 0 {
this.FailField("portAfter", "请输入跳转后端口")
return
}
config.PortAfter = params.PortAfter
config.PortAfterScheme = params.PortAfterScheme
} }
params.Must. params.Must.
@@ -99,19 +182,16 @@ func (this *CreatePopupAction) RunPost(params struct {
this.Fail("匹配条件校验失败:" + err.Error()) this.Fail("匹配条件校验失败:" + err.Error())
} }
} }
config.Conds = conds
this.Data["redirect"] = maps.Map{ // 校验配置
"mode": params.Mode, err := config.Init()
"status": params.Status, if err != nil {
"beforeURL": params.BeforeURL, this.Fail("配置校验失败:" + err.Error())
"afterURL": params.AfterURL, return
"matchPrefix": params.MatchPrefix,
"matchRegexp": params.MatchRegexp,
"keepRequestURI": params.KeepRequestURI,
"keepArgs": params.KeepArgs,
"conds": conds,
"isOn": params.IsOn,
} }
this.Data["redirect"] = config
this.Success() this.Success()
} }

View File

@@ -1,11 +1,10 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus
package users package users
import ( import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
) )
@@ -17,33 +16,6 @@ func (this *PlansAction) RunPost(params struct {
UserId int64 UserId int64
ServerId int64 ServerId int64
}) { }) {
if !teaconst.IsPlus || params.UserId <= 0 { this.Data["plans"] = []maps.Map{}
this.Data["plans"] = []maps.Map{}
this.Success()
}
// TODO 优化用户套餐查询
userPlansResp, err := this.RPC().UserPlanRPC().FindAllEnabledUserPlansForServer(this.AdminContext(), &pb.FindAllEnabledUserPlansForServerRequest{
UserId: params.UserId,
ServerId: params.ServerId,
})
if err != nil {
this.ErrorPage(err)
return
}
var userPlanMaps = []maps.Map{}
for _, userPlan := range userPlansResp.UserPlans {
if userPlan.Plan == nil {
continue
}
userPlanMaps = append(userPlanMaps, maps.Map{
"id": userPlan.Id,
"name": userPlan.Plan.Name,
"dayTo": userPlan.DayTo,
})
}
this.Data["plans"] = userPlanMaps
this.Success() this.Success()
} }

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/dbs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"net" "net"
"os"
"strings" "strings"
"time" "time"
) )
@@ -31,10 +32,21 @@ func (this *DetectDBAction) RunPost(params struct{}) {
localPort = "3306" localPort = "3306"
var username = "root" 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{ db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
Driver: "mysql", Driver: "mysql",
Dsn: username + ":" + pass + "@tcp(" + configutils.QuoteIP(localHost) + ":" + localPort + ")/edges11111", Dsn: username + ":" + pass + "@tcp(" + configutils.QuoteIP(localHost) + ":" + localPort + ")/edges",
Prefix: "", Prefix: "",
}) })
if err == nil { if err == nil {

View File

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

View File

@@ -17,13 +17,17 @@ import (
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/gosock/pkg/gosock" "github.com/iwind/gosock/pkg/gosock"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"io"
"os" "os"
"os/exec" "os/exec"
"strings"
"time" "time"
) )
type InstallAction struct { type InstallAction struct {
actionutils.ParentAction actionutils.ParentAction
apiSetupFinished bool
} }
func (this *InstallAction) RunPost(params struct { func (this *InstallAction) RunPost(params struct {
@@ -40,7 +44,7 @@ func (this *InstallAction) RunPost(params struct {
// API节点配置 // API节点配置
currentStatusText = "正在检查API节点配置" currentStatusText = "正在检查API节点配置"
apiNodeMap := maps.Map{} var apiNodeMap = maps.Map{}
err := json.Unmarshal(params.ApiNodeJSON, &apiNodeMap) err := json.Unmarshal(params.ApiNodeJSON, &apiNodeMap)
if err != nil { if err != nil {
this.Fail("API节点配置数据解析错误请刷新页面后重新尝试安装错误信息" + err.Error()) this.Fail("API节点配置数据解析错误请刷新页面后重新尝试安装错误信息" + err.Error())
@@ -48,7 +52,7 @@ func (this *InstallAction) RunPost(params struct {
// 数据库 // 数据库
currentStatusText = "正在检查数据库配置" currentStatusText = "正在检查数据库配置"
dbMap := maps.Map{} var dbMap = maps.Map{}
err = json.Unmarshal(params.DbJSON, &dbMap) err = json.Unmarshal(params.DbJSON, &dbMap)
if err != nil { if err != nil {
this.Fail("数据库配置数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error()) this.Fail("数据库配置数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error())
@@ -56,14 +60,14 @@ func (this *InstallAction) RunPost(params struct {
// 管理员 // 管理员
currentStatusText = "正在检查管理员配置" currentStatusText = "正在检查管理员配置"
adminMap := maps.Map{} var adminMap = maps.Map{}
err = json.Unmarshal(params.AdminJSON, &adminMap) err = json.Unmarshal(params.AdminJSON, &adminMap)
if err != nil { if err != nil {
this.Fail("管理员数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error()) this.Fail("管理员数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error())
} }
// 安装API节点 // 安装API节点
mode := apiNodeMap.GetString("mode") var mode = apiNodeMap.GetString("mode")
if mode == "new" { if mode == "new" {
currentStatusText = "准备启动新API节点" 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"} { for _, dir := range []string{"edge-api", "edge-api/configs", "edge-api/bin"} {
apiNodeDir := Tea.Root + "/" + dir apiNodeDir := Tea.Root + "/" + dir
_, err = os.Stat(apiNodeDir) _, 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{ dbConfig := &dbs.Config{
DBs: map[string]*dbs.DBConfig{ DBs: map[string]*dbs.DBConfig{
"prod": { "prod": {
@@ -108,7 +112,7 @@ func (this *InstallAction) RunPost(params struct {
// 生成备份文件 // 生成备份文件
homeDir, _ := os.UserHomeDir() homeDir, _ := os.UserHomeDir()
backupDirs := []string{"/etc/edge-api"} var backupDirs = []string{"/etc/edge-api"}
if len(homeDir) > 0 { if len(homeDir) > 0 {
backupDirs = append(backupDirs, homeDir+"/.edge-api") backupDirs = append(backupDirs, homeDir+"/.edge-api")
} }
@@ -151,15 +155,21 @@ func (this *InstallAction) RunPost(params struct {
var resultMap = maps.Map{} var resultMap = maps.Map{}
logs.Println("[INSTALL]setup edge-api") 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")+"\"") this.apiSetupFinished = false
output := bytes.NewBuffer([]byte{}) 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 cmd.Stdout = output
// 试图读取执行日志
go this.startReadingAPIInstallLog()
err = cmd.Run() err = cmd.Run()
this.apiSetupFinished = true
if err != nil { if err != nil {
this.Fail("安装失败:" + err.Error()) this.Fail("安装失败:" + err.Error())
} }
resultData := output.Bytes() var resultData = output.Bytes()
err = json.Unmarshal(resultData, &resultMap) err = json.Unmarshal(resultData, &resultMap)
if err != nil { if err != nil {
this.Fail("安装节点时返回数据错误:" + err.Error() + "(" + string(resultData) + ")") this.Fail("安装节点时返回数据错误:" + err.Error() + "(" + string(resultData) + ")")
@@ -175,7 +185,7 @@ func (this *InstallAction) RunPost(params struct {
// 关闭正在运行的API节点防止冲突 // 关闭正在运行的API节点防止冲突
logs.Println("[INSTALL]stop edge-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() _ = cmd.Run()
} }
@@ -183,7 +193,7 @@ func (this *InstallAction) RunPost(params struct {
currentStatusText = "正在启动API节点" currentStatusText = "正在启动API节点"
logs.Println("[INSTALL]start edge-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() err = cmd.Start()
if err != nil { if err != nil {
this.Fail("API节点启动失败" + err.Error()) this.Fail("API节点启动失败" + err.Error())
@@ -220,7 +230,7 @@ func (this *InstallAction) RunPost(params struct {
} }
// 写入API节点配置完成安装 // 写入API节点配置完成安装
apiConfig := &configs.APIConfig{ var apiConfig = &configs.APIConfig{
RPC: struct { RPC: struct {
Endpoints []string `yaml:"endpoints"` Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"` DisableUpdate bool `yaml:"disableUpdate"`
@@ -283,7 +293,7 @@ func (this *InstallAction) RunPost(params struct {
this.Success() this.Success()
} else if mode == "old" { } else if mode == "old" {
// 构造RPC // 构造RPC
apiConfig := &configs.APIConfig{ var apiConfig = &configs.APIConfig{
RPC: struct { RPC: struct {
Endpoints []string `yaml:"endpoints"` Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"` 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{ _, err = client.AdminRPC().CreateOrUpdateAdmin(ctx, &pb.CreateOrUpdateAdminRequest{
Username: adminMap.GetString("username"), Username: adminMap.GetString("username"),
Password: adminMap.GetString("password"), Password: adminMap.GetString("password"),
@@ -344,3 +354,73 @@ func (this *InstallAction) RunPost(params struct {
this.Fail("错误的API节点模式'" + mode + "'") 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)). Get("/otpQrcode", new(OtpQrcodeAction)).
// AccessKeys // AccessKeys
Prefix("/users/accessKeys"). Prefix("/users/accesskeys").
Get("", new(accesskeys.IndexAction)). Get("", new(accesskeys.IndexAction)).
GetPost("/createPopup", new(accesskeys.CreatePopupAction)). GetPost("/createPopup", new(accesskeys.CreatePopupAction)).
Post("/delete", new(accesskeys.DeleteAction)). Post("/delete", new(accesskeys.DeleteAction)).

View File

@@ -4,6 +4,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/events" "github.com/TeaOSLab/EdgeAdmin/internal/events"
nodes "github.com/TeaOSLab/EdgeAdmin/internal/rpc" nodes "github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/utils" "github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
@@ -145,7 +146,7 @@ func checkRequestSecurity(securityConfig *systemconfigs.SecurityConfig, req *htt
if err == nil && len(realDomain) > 0 { if err == nil && len(realDomain) > 0 {
domain = realDomain domain = realDomain
} }
if !lists.ContainsString(securityConfig.AllowDomains, domain) { if !configutils.MatchDomains(securityConfig.AllowDomains, domain) {
return false return false
} }
} }

View File

@@ -19,7 +19,6 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/logs" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/logs"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/regions" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/regions"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/regions/items"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/tasks" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/tasks"
// 通用 // 通用
@@ -125,7 +124,6 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ui" _ "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/updates"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/upgrade" _ "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" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/recover"

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

1
web/public/js/components/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*-plus.js

View File

@@ -41,7 +41,7 @@ Vue.component("api-node-addresses-box", {
<input type="hidden" :name="vName" :value="JSON.stringify(addrs)"/> <input type="hidden" :name="vName" :value="JSON.stringify(addrs)"/>
<div v-if="addrs.length > 0"> <div v-if="addrs.length > 0">
<div> <div>
<div v-for="(addr, index) in addrs" class="ui label small"> <div v-for="(addr, index) in addrs" class="ui label small basic">
{{addr.protocol}}://{{addr.host.quoteIP()}}:{{addr.portRange}}</span> {{addr.protocol}}://{{addr.host.quoteIP()}}:{{addr.portRange}}</span>
<a href="" title="修改" @click.prevent="updateAddr(index, addr)"><i class="icon pencil small"></i></a> <a href="" title="修改" @click.prevent="updateAddr(index, addr)"><i class="icon pencil small"></i></a>
<a href="" title="删除" @click.prevent="removeAddr(index)"><i class="icon remove"></i></a> <a href="" title="删除" @click.prevent="removeAddr(index)"><i class="icon remove"></i></a>

View File

@@ -0,0 +1,16 @@
Vue.component("bits-var", {
props: ["v-bits"],
data: function () {
let bits = this.vBits
if (typeof bits != "number") {
bits = 0
}
let format = teaweb.splitFormat(teaweb.formatBits(bits))
return {
format: format
}
},
template:`<var class="normal">
<span>{{format[0]}}</span>{{format[1]}}
</var>`
})

View File

@@ -0,0 +1,16 @@
Vue.component("bytes-var", {
props: ["v-bytes"],
data: function () {
let bytes = this.vBytes
if (typeof bytes != "number") {
bytes = 0
}
let format = teaweb.splitFormat(teaweb.formatBytes(bytes))
return {
format: format
}
},
template:`<var class="normal">
<span>{{format[0]}}</span>{{format[1]}}
</var>`
})

View File

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

View File

@@ -1,5 +1,5 @@
Vue.component("datepicker", { Vue.component("datepicker", {
props: ["v-name", "v-value", "v-bottom-left"], props: ["value", "v-name", "name", "v-value", "v-bottom-left", "placeholder"],
mounted: function () { mounted: function () {
let that = this let that = this
teaweb.datepicker(this.$refs.dayInput, function (v) { teaweb.datepicker(this.$refs.dayInput, function (v) {
@@ -9,26 +9,51 @@ Vue.component("datepicker", {
}, },
data: function () { data: function () {
let name = this.vName let name = this.vName
if (name == null) {
name = this.name
}
if (name == null) { if (name == null) {
name = "day" name = "day"
} }
let day = this.vValue let day = this.vValue
if (day == null) { if (day == null) {
day = "" day = this.value
if (day == null) {
day = ""
}
}
let placeholder = "YYYY-MM-DD"
if (this.placeholder != null) {
placeholder = this.placeholder
} }
return { return {
name: name, realName: name,
realPlaceholder: placeholder,
day: day day: day
} }
}, },
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: { methods: {
change: function () { change: function () {
this.$emit("input", this.day) // support v-model事件触发需要在 change 之前
this.$emit("change", this.day) this.$emit("change", this.day)
} }
}, },
template: `<div style="display: inline-block"> template: `<div style="display: inline-block">
<input type="text" :name="name" v-model="day" placeholder="YYYY-MM-DD" style="width:8.6em" maxlength="10" @input="change" ref="dayInput" autocomplete="off"/> <input type="text" :name="realName" v-model="day" :placeholder="realPlaceholder" style="width:8.6em" maxlength="10" @input="change" ref="dayInput" autocomplete="off"/>
</div>` </div>`
}) })

View File

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

View File

@@ -24,7 +24,7 @@ Vue.component("download-link", {
} else { } else {
let e = document.getElementById(this.vElement) let e = document.getElementById(this.vElement)
if (e == null) { if (e == null) {
teaweb.warn("找不到要下载的内容") // 不提示错误,因为此时可能页面未加载完整
return return
} }
text = e.innerText text = e.innerText

View File

@@ -1,7 +1,7 @@
// 启用状态标签 // 启用状态标签
Vue.component("label-on", { Vue.component("label-on", {
props: ["v-is-on"], props: ["v-is-on"],
template: '<div><span v-if="vIsOn" class="ui label tiny green basic">已启用</span><span v-if="!vIsOn" class="ui label tiny red basic">已停用</span></div>' template: '<div><span v-if="vIsOn" class="green">已启用</span><span v-if="!vIsOn" class="red">已停用</span></div>'
}) })
// 文字代码标签 // 文字代码标签

View File

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

View File

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

View File

@@ -1,162 +0,0 @@
Vue.component("plan-bandwidth-ranges", {
props: ["v-ranges"],
data: function () {
let ranges = this.vRanges
if (ranges == null) {
ranges = []
}
return {
ranges: ranges,
isAdding: false,
minMB: "",
maxMB: "",
pricePerMB: "",
totalPrice: "",
addingRange: {
minMB: 0,
maxMB: 0,
pricePerMB: 0,
totalPrice: 0
}
}
},
methods: {
add: function () {
this.isAdding = !this.isAdding
let that = this
setTimeout(function () {
that.$refs.minMB.focus()
})
},
cancelAdding: function () {
this.isAdding = false
},
confirm: function () {
this.isAdding = false
this.minMB = ""
this.maxMB = ""
this.pricePerMB = ""
this.totalPrice = ""
this.ranges.push(this.addingRange)
this.ranges.$sort(function (v1, v2) {
if (v1.minMB < v2.minMB) {
return -1
}
if (v1.minMB == v2.minMB) {
if (v2.maxMB == 0 || v1.maxMB < v2.maxMB) {
return -1
}
return 0
}
return 1
})
this.change()
this.addingRange = {
minMB: 0,
maxMB: 0,
pricePerMB: 0,
totalPrice: 0
}
},
remove: function (index) {
this.ranges.$remove(index)
this.change()
},
change: function () {
this.$emit("change", this.ranges)
}
},
watch: {
minMB: function (v) {
let minMB = parseInt(v.toString())
if (isNaN(minMB) || minMB < 0) {
minMB = 0
}
this.addingRange.minMB = minMB
},
maxMB: function (v) {
let maxMB = parseInt(v.toString())
if (isNaN(maxMB) || maxMB < 0) {
maxMB = 0
}
this.addingRange.maxMB = maxMB
},
pricePerMB: function (v) {
let pricePerMB = parseFloat(v.toString())
if (isNaN(pricePerMB) || pricePerMB < 0) {
pricePerMB = 0
}
this.addingRange.pricePerMB = pricePerMB
},
totalPrice: function (v) {
let totalPrice = parseFloat(v.toString())
if (isNaN(totalPrice) || totalPrice < 0) {
totalPrice = 0
}
this.addingRange.totalPrice = totalPrice
}
},
template: `<div>
<!-- 已有价格 -->
<div v-if="ranges.length > 0">
<div class="ui label basic small" v-for="(range, index) in ranges" style="margin-bottom: 0.5em">
{{range.minMB}}MB - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>&infin;</span> &nbsp; 价格:<span v-if="range.totalPrice > 0">{{range.totalPrice}}元</span><span v-else="">{{range.pricePerMB}}元/MB</span>
&nbsp; <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
</div>
<div class="ui divider"></div>
</div>
<!-- 添加 -->
<div v-if="isAdding">
<table class="ui table">
<tr>
<td class="title">带宽下限 *</td>
<td>
<div class="ui input right labeled">
<input type="text" placeholder="最小带宽" style="width: 7em" maxlength="10" ref="minMB" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="minMB"/>
<span class="ui label">MB</span>
</div>
</td>
</tr>
<tr>
<td class="title">带宽上限 *</td>
<td>
<div class="ui input right labeled">
<input type="text" placeholder="最大带宽" style="width: 7em" maxlength="10" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="maxMB"/>
<span class="ui label">MB</span>
</div>
<p class="comment">如果填0表示上不封顶。</p>
</td>
</tr>
<tr>
<td>总价格</td>
<td>
<div class="ui input right labeled">
<input type="text" placeholder="总价格" style="width: 7em" maxlength="10" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="totalPrice"/>
<span class="ui label">元/MB</span>
</div>
<p class="comment">和单位价格二选一。</p>
</td>
</tr>
<tr>
<td class="title">单位价格</td>
<td>
<div class="ui input right labeled">
<input type="text" placeholder="单位价格" style="width: 7em" maxlength="10" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="pricePerMB"/>
<span class="ui label">元/MB</span>
</div>
<p class="comment">和总价格二选一。如果设置了单位价格,那么"总价格 = 单位价格 x 带宽"。</p>
</td>
</tr>
</table>
<button class="ui button small" type="button" @click.prevent="confirm">确定</button> &nbsp;
<a href="" title="取消" @click.prevent="cancelAdding"><i class="icon remove small"></i></a>
</div>
<!-- 按钮 -->
<div v-if="!isAdding">
<button class="ui button small" type="button" @click.prevent="add">+</button>
</div>
</div>`
})

View File

@@ -1,220 +0,0 @@
// 套餐价格配置
Vue.component("plan-price-config-box", {
props: ["v-price-type", "v-monthly-price", "v-seasonally-price", "v-yearly-price", "v-traffic-price", "v-bandwidth-price", "v-disable-period"],
data: function () {
let priceType = this.vPriceType
if (priceType == null) {
priceType = "bandwidth"
}
// 按时间周期计费
let monthlyPriceNumber = 0
let monthlyPrice = this.vMonthlyPrice
if (monthlyPrice == null || monthlyPrice <= 0) {
monthlyPrice = ""
} else {
monthlyPrice = monthlyPrice.toString()
monthlyPriceNumber = parseFloat(monthlyPrice)
if (isNaN(monthlyPriceNumber)) {
monthlyPriceNumber = 0
}
}
let seasonallyPriceNumber = 0
let seasonallyPrice = this.vSeasonallyPrice
if (seasonallyPrice == null || seasonallyPrice <= 0) {
seasonallyPrice = ""
} else {
seasonallyPrice = seasonallyPrice.toString()
seasonallyPriceNumber = parseFloat(seasonallyPrice)
if (isNaN(seasonallyPriceNumber)) {
seasonallyPriceNumber = 0
}
}
let yearlyPriceNumber = 0
let yearlyPrice = this.vYearlyPrice
if (yearlyPrice == null || yearlyPrice <= 0) {
yearlyPrice = ""
} else {
yearlyPrice = yearlyPrice.toString()
yearlyPriceNumber = parseFloat(yearlyPrice)
if (isNaN(yearlyPriceNumber)) {
yearlyPriceNumber = 0
}
}
// 按流量计费
let trafficPrice = this.vTrafficPrice
let trafficPriceBaseNumber = 0
if (trafficPrice != null) {
trafficPriceBaseNumber = trafficPrice.base
} else {
trafficPrice = {
base: 0
}
}
let trafficPriceBase = ""
if (trafficPriceBaseNumber > 0) {
trafficPriceBase = trafficPriceBaseNumber.toString()
}
// 按带宽计费
let bandwidthPrice = this.vBandwidthPrice
if (bandwidthPrice == null) {
bandwidthPrice = {
percentile: 95,
ranges: []
}
} else if (bandwidthPrice.ranges == null) {
bandwidthPrice.ranges = []
}
return {
priceType: priceType,
monthlyPrice: monthlyPrice,
seasonallyPrice: seasonallyPrice,
yearlyPrice: yearlyPrice,
monthlyPriceNumber: monthlyPriceNumber,
seasonallyPriceNumber: seasonallyPriceNumber,
yearlyPriceNumber: yearlyPriceNumber,
trafficPriceBase: trafficPriceBase,
trafficPrice: trafficPrice,
bandwidthPrice: bandwidthPrice,
bandwidthPercentile: bandwidthPrice.percentile
}
},
methods: {
changeBandwidthPriceRanges: function (ranges) {
this.bandwidthPrice.ranges = ranges
}
},
watch: {
monthlyPrice: function (v) {
let price = parseFloat(v)
if (isNaN(price)) {
price = 0
}
this.monthlyPriceNumber = price
},
seasonallyPrice: function (v) {
let price = parseFloat(v)
if (isNaN(price)) {
price = 0
}
this.seasonallyPriceNumber = price
},
yearlyPrice: function (v) {
let price = parseFloat(v)
if (isNaN(price)) {
price = 0
}
this.yearlyPriceNumber = price
},
trafficPriceBase: function (v) {
let price = parseFloat(v)
if (isNaN(price)) {
price = 0
}
this.trafficPrice.base = price
},
bandwidthPercentile: function (v) {
let percentile = parseInt(v)
if (isNaN(percentile) || percentile <= 0) {
percentile = 95
} else if (percentile > 100) {
percentile = 100
}
this.bandwidthPrice.percentile = percentile
}
},
template: `<div>
<input type="hidden" name="priceType" :value="priceType"/>
<input type="hidden" name="monthlyPrice" :value="monthlyPriceNumber"/>
<input type="hidden" name="seasonallyPrice" :value="seasonallyPriceNumber"/>
<input type="hidden" name="yearlyPrice" :value="yearlyPriceNumber"/>
<input type="hidden" name="trafficPriceJSON" :value="JSON.stringify(trafficPrice)"/>
<input type="hidden" name="bandwidthPriceJSON" :value="JSON.stringify(bandwidthPrice)"/>
<div>
<radio :v-value="'bandwidth'" :value="priceType" v-model="priceType">&nbsp;按带宽</radio> &nbsp;
<radio :v-value="'traffic'" :value="priceType" v-model="priceType">&nbsp;按流量</radio> &nbsp;
<radio :v-value="'period'" :value="priceType" v-model="priceType" v-show="typeof(vDisablePeriod) != 'boolean' || !vDisablePeriod">&nbsp;按时间周期</radio>
</div>
<!-- 按时间周期 -->
<div v-show="priceType == 'period'">
<div class="ui divider"></div>
<table class="ui table">
<tr>
<td class="title">月度价格</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 7em" maxlength="10" v-model="monthlyPrice"/>
<span class="ui label">元</span>
</div>
</td>
</tr>
<tr>
<td class="title">季度价格</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 7em" maxlength="10" v-model="seasonallyPrice"/>
<span class="ui label">元</span>
</div>
</td>
</tr>
<tr>
<td class="title">年度价格</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 7em" maxlength="10" v-model="yearlyPrice"/>
<span class="ui label">元</span>
</div>
</td>
</tr>
</table>
</div>
<!-- 按流量 -->
<div v-show="priceType =='traffic'">
<div class="ui divider"></div>
<table class="ui table">
<tr>
<td class="title">基础流量费用 *</td>
<td>
<div class="ui input right labeled">
<input type="text" v-model="trafficPriceBase" maxlength="10" style="width: 7em"/>
<span class="ui label">元/GB</span>
</div>
</td>
</tr>
</table>
</div>
<!-- 按带宽 -->
<div v-show="priceType == 'bandwidth'">
<div class="ui divider"></div>
<table class="ui table">
<tr>
<td class="title">带宽百分位 *</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 4em" maxlength="3" v-model="bandwidthPercentile"/>
<span class="ui label">th</span>
</div>
</td>
</tr>
<tr>
<td>带宽价格</td>
<td>
<plan-bandwidth-ranges :v-ranges="bandwidthPrice.ranges" @change="changeBandwidthPriceRanges"></plan-bandwidth-ranges>
</td>
</tr>
</table>
</div>
</div>`
})

View File

@@ -1,6 +1,6 @@
// 域名列表 // 域名列表
Vue.component("domains-box", { Vue.component("domains-box", {
props: ["v-domains", "name"], props: ["v-domains", "name", "v-support-wildcard"],
data: function () { data: function () {
let domains = this.vDomains let domains = this.vDomains
if (domains == null) { if (domains == null) {
@@ -11,11 +11,47 @@ Vue.component("domains-box", {
if (this.name != null && typeof this.name == "string") { if (this.name != null && typeof this.name == "string") {
realName = this.name realName = this.name
} }
let supportWildcard = true
if (typeof this.vSupportWildcard == "boolean") {
supportWildcard = this.vSupportWildcard
}
return { return {
domains: domains, domains: domains,
mode: "single", // single | batch
batchDomains: "",
isAdding: false, isAdding: false,
addingDomain: "", addingDomain: "",
realName: realName
isEditing: false,
editingIndex: -1,
realName: realName,
supportWildcard: supportWildcard
}
},
watch: {
vSupportWildcard: function (v) {
if (typeof v == "boolean") {
this.supportWildcard = v
}
},
mode: function (mode) {
let that = this
setTimeout(function () {
if (mode == "single") {
if (that.$refs.addingDomain != null) {
that.$refs.addingDomain.focus()
}
} else if (mode == "batch") {
if (that.$refs.batchDomains != null) {
that.$refs.batchDomains.focus()
}
}
}, 100)
} }
}, },
methods: { methods: {
@@ -27,6 +63,11 @@ Vue.component("domains-box", {
}, 100) }, 100)
}, },
confirm: function () { confirm: function () {
if (this.mode == "batch") {
this.confirmBatch()
return
}
let that = this let that = this
// 删除其中的空格 // 删除其中的空格
@@ -39,54 +80,155 @@ Vue.component("domains-box", {
return return
} }
// 基本校验 // 基本校验
if (this.addingDomain[0] == "~") { if (this.supportWildcard) {
let expr = this.addingDomain.substring(1) if (this.addingDomain[0] == "~") {
try { let expr = this.addingDomain.substring(1)
new RegExp(expr) try {
} catch (e) { new RegExp(expr)
teaweb.warn("正则表达式错误:" + e.message, function () { } catch (e) {
teaweb.warn("正则表达式错误:" + e.message, function () {
that.$refs.addingDomain.focus()
})
return
}
}
} else {
if (/[*~^]/.test(this.addingDomain)) {
teaweb.warn("当前只支持添加普通域名,域名中不能含有特殊符号", function () {
that.$refs.addingDomain.focus() that.$refs.addingDomain.focus()
}) })
return return
} }
} }
this.domains.push(this.addingDomain) if (this.isEditing && this.editingIndex >= 0) {
this.domains[this.editingIndex] = this.addingDomain
} else {
this.domains.push(this.addingDomain)
}
this.cancel() this.cancel()
this.change()
},
confirmBatch: function () {
let domains = this.batchDomains.split("\n")
let realDomains = []
let that = this
let hasProblems = false
domains.forEach(function (domain) {
if (hasProblems) {
return
}
if (domain.length == 0) {
return
}
if (that.supportWildcard) {
if (domain == "~") {
let expr = domain.substring(1)
try {
new RegExp(expr)
} catch (e) {
hasProblems = true
teaweb.warn("正则表达式错误:" + e.message, function () {
that.$refs.batchDomains.focus()
})
return
}
}
} else {
if (/[*~^]/.test(domain)) {
hasProblems = true
teaweb.warn("当前只支持添加普通域名,域名中不能含有特殊符号", function () {
that.$refs.batchDomains.focus()
})
return
}
}
realDomains.push(domain)
})
if (hasProblems) {
return
}
if (realDomains.length == 0) {
teaweb.warn("请输入要添加的域名", function () {
that.$refs.batchDomains.focus()
})
return
}
realDomains.forEach(function (domain) {
that.domains.push(domain)
})
this.cancel()
this.change()
},
edit: function (index) {
this.addingDomain = this.domains[index]
this.isEditing = true
this.editingIndex = index
let that = this
setTimeout(function () {
that.$refs.addingDomain.focus()
}, 50)
}, },
remove: function (index) { remove: function (index) {
this.domains.$remove(index) this.domains.$remove(index)
this.change()
}, },
cancel: function () { cancel: function () {
this.isAdding = false this.isAdding = false
this.mode = "single"
this.batchDomains = ""
this.isEditing = false
this.editingIndex = -1
this.addingDomain = "" this.addingDomain = ""
},
change: function () {
this.$emit("change", this.domains)
} }
}, },
template: `<div> template: `<div>
<input type="hidden" :name="realName" :value="JSON.stringify(domains)"/> <input type="hidden" :name="realName" :value="JSON.stringify(domains)"/>
<div v-if="domains.length > 0"> <div v-if="domains.length > 0">
<span class="ui label small basic" v-for="(domain, index) in domains"> <span class="ui label small basic" v-for="(domain, index) in domains" :class="{blue: index == editingIndex}">
<span v-if="domain.length > 0 && domain[0] == '~'" class="grey" style="font-style: normal">[正则]</span> <span v-if="domain.length > 0 && domain[0] == '~'" class="grey" style="font-style: normal">[正则]</span>
<span v-if="domain.length > 0 && domain[0] == '.'" class="grey" style="font-style: normal">[后缀]</span> <span v-if="domain.length > 0 && domain[0] == '.'" class="grey" style="font-style: normal">[后缀]</span>
<span v-if="domain.length > 0 && domain[0] == '*'" class="grey" style="font-style: normal">[泛域名]</span> <span v-if="domain.length > 0 && domain[0] == '*'" class="grey" style="font-style: normal">[泛域名]</span>
{{domain}} {{domain}}
&nbsp; <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a> <span v-if="!isAdding && !isEditing">
&nbsp; <a href="" title="修改" @click.prevent="edit(index)"><i class="icon pencil small"></i></a>
&nbsp; <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
</span>
<span v-if="isAdding || isEditing">
&nbsp; <a class="disabled"><i class="icon pencil small"></i></a>
&nbsp; <a class="disabled"><i class="icon remove small"></i></a>
</span>
</span> </span>
<div class="ui divider"></div> <div class="ui divider"></div>
</div> </div>
<div v-if="isAdding"> <div v-if="isAdding || isEditing">
<div class="ui fields"> <div class="ui fields">
<div class="ui field" v-if="isAdding">
<select class="ui dropdown" v-model="mode">
<option value="single">单个</option>
<option value="batch">批量</option>
</select>
</div>
<div class="ui field"> <div class="ui field">
<input type="text" v-model="addingDomain" @keyup.enter="confirm()" @keypress.enter.prevent="1" ref="addingDomain" placeholder="*.xxx.com" size="30"/> <div v-show="mode == 'single'">
<input type="text" v-model="addingDomain" @keyup.enter="confirm()" @keypress.enter.prevent="1" @keydown.esc="cancel()" ref="addingDomain" :placeholder="supportWildcard ? 'example.com、*.example.com' : 'example.com、www.example.com'" size="30" maxlength="100"/>
</div>
<div v-show="mode == 'batch'">
<textarea cols="30" v-model="batchDomains" placeholder="example1.com\nexample2.com\n每行一个域名" ref="batchDomains"></textarea>
</div>
</div> </div>
<div class="ui field"> <div class="ui field">
<button class="ui button tiny" type="button" @click.prevent="confirm">确定</button> <button class="ui button tiny" type="button" @click.prevent="confirm">确定</button>
&nbsp; <a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a> &nbsp; <a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
</div> </div>
</div> </div>
<p class="comment">支持普通域名(<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 class="ui divider"></div>
</div> </div>
<div style="margin-top: 0.5em" v-if="!isAdding"> <div style="margin-top: 0.5em" v-if="!isAdding">

View File

@@ -59,7 +59,7 @@ Vue.component("firewall-syn-flood-config-box", {
<td class="title">启用</td> <td class="title">启用</td>
<td> <td>
<checkbox v-model="config.isOn"></checkbox> <checkbox v-model="config.isOn"></checkbox>
<p class="comment">启用后WAF将会尝试自动检测并阻止SYN Flood攻击。此功能需要节点已安装并启用Firewalld。</p> <p class="comment">启用后WAF将会尝试自动检测并阻止SYN Flood攻击。此功能需要节点已安装并启用nftables或Firewalld。</p>
</td> </td>
</tr> </tr>
<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> <prior-checkbox :v-config="accessLog" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || accessLog.isPrior"> <tbody v-show="(!vIsLocation && !vIsGroup) || accessLog.isPrior">
<tr> <tr>
<td class="title">启访问日志</td> <td class="title">启访问日志</td>
<td> <td>
<div class="ui checkbox"> <div class="ui checkbox">
<input type="checkbox" v-model="accessLog.isOn"/> <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> <a class="ui label basic" :class="{disabled: keyword.length == 0}" @click.prevent="cleanKeyword"><i class="icon remove small"></i></a>
</div> </div>
</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>
<div class="ui fields inline" style="margin-top: 0.5em"> <div class="ui fields inline" style="margin-top: 0.5em">
<div class="ui field"> <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> <prior-checkbox :v-config="charsetConfig" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || charsetConfig.isPrior"> <tbody v-show="(!vIsLocation && !vIsGroup) || charsetConfig.isPrior">
<tr> <tr>
<td class="title">启用</td> <td class="title">启用字符编码</td>
<td> <td>
<div class="ui checkbox"> <div class="ui checkbox">
<input type="checkbox" v-model="charsetConfig.isOn"/> <input type="checkbox" v-model="charsetConfig.isOn"/>

View File

@@ -22,7 +22,7 @@ Vue.component("http-compression-config-box", {
brotliRef: null, brotliRef: null,
minLength: {count: 0, "unit": "kb"}, minLength: {count: 0, "unit": "kb"},
maxLength: {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"], extensions: [".js", ".json", ".html", ".htm", ".xml", ".css", ".woff2", ".txt"],
conds: null conds: null
} }
@@ -153,7 +153,7 @@ Vue.component("http-compression-config-box", {
<prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox> <prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior"> <tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior">
<tr> <tr>
<td class="title">启用</td> <td class="title">启用内容压缩</td>
<td> <td>
<div class="ui checkbox"> <div class="ui checkbox">
<input type="checkbox" value="1" v-model="config.isOn"/> <input type="checkbox" value="1" v-model="config.isOn"/>

View File

@@ -8,7 +8,6 @@ Vue.component("http-firewall-rule-label", {
}, },
methods: { methods: {
showErr: function (err) { showErr: function (err) {
teaweb.popupTip("规则校验错误,请修正:<span class=\"red\">" + teaweb.encodeHTML(err) + "</span>") teaweb.popupTip("规则校验错误,请修正:<span class=\"red\">" + teaweb.encodeHTML(err) + "</span>")
}, },
@@ -24,7 +23,8 @@ Vue.component("http-firewall-rule-label", {
<!-- refererBlock --> <!-- refererBlock -->
<span v-if="rule.param == '\${refererBlock}'"> <span v-if="rule.param == '\${refererBlock}'">
{{rule.checkpointOptions.allowDomains}} <span v-if="rule.checkpointOptions.allowDomains != null && rule.checkpointOptions.allowDomains.length > 0">允许{{rule.checkpointOptions.allowDomains}}</span>
<span v-if="rule.checkpointOptions.denyDomains != null && rule.checkpointOptions.denyDomains.length > 0">禁止{{rule.checkpointOptions.denyDomains}}</span>
</span> </span>
<span v-else> <span v-else>

View File

@@ -14,6 +14,7 @@ Vue.component("http-firewall-rules-box", {
window.UPDATING_RULE = null window.UPDATING_RULE = null
let that = this let that = this
teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, { teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, {
height: "30em",
callback: function (resp) { callback: function (resp) {
that.rules.push(resp.data.rule) that.rules.push(resp.data.rule)
} }
@@ -23,6 +24,7 @@ Vue.component("http-firewall-rules-box", {
window.UPDATING_RULE = rule window.UPDATING_RULE = rule
let that = this let that = this
teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, { teaweb.popup("/servers/components/waf/createRulePopup?type=" + this.vType, {
height: "30em",
callback: function (resp) { callback: function (resp) {
Vue.set(that.rules, index, resp.data.rule) Vue.set(that.rules, index, resp.data.rule)
} }
@@ -48,7 +50,8 @@ Vue.component("http-firewall-rules-box", {
<!-- refererBlock --> <!-- refererBlock -->
<span v-if="rule.param == '\${refererBlock}'"> <span v-if="rule.param == '\${refererBlock}'">
{{rule.checkpointOptions.allowDomains}} <span v-if="rule.checkpointOptions.allowDomains != null && rule.checkpointOptions.allowDomains.length > 0">允许{{rule.checkpointOptions.allowDomains}}</span>
<span v-if="rule.checkpointOptions.denyDomains != null && rule.checkpointOptions.denyDomains.length > 0">禁止{{rule.checkpointOptions.denyDomains}}</span>
</span> </span>
<span v-else> <span v-else>

View File

@@ -236,6 +236,7 @@ Vue.component("http-firewall-checkpoint-referer-block", {
let allowEmpty = true let allowEmpty = true
let allowSameDomain = true let allowSameDomain = true
let allowDomains = [] let allowDomains = []
let denyDomains = []
let options = {} let options = {}
if (window.parent.UPDATING_RULE != null) { if (window.parent.UPDATING_RULE != null) {
@@ -254,6 +255,9 @@ Vue.component("http-firewall-checkpoint-referer-block", {
if (options.allowDomains != null && typeof (options.allowDomains) == "object") { if (options.allowDomains != null && typeof (options.allowDomains) == "object") {
allowDomains = options.allowDomains allowDomains = options.allowDomains
} }
if (options.denyDomains != null && typeof (options.denyDomains) == "object") {
denyDomains = options.denyDomains
}
let that = this let that = this
setTimeout(function () { setTimeout(function () {
@@ -264,6 +268,7 @@ Vue.component("http-firewall-checkpoint-referer-block", {
allowEmpty: allowEmpty, allowEmpty: allowEmpty,
allowSameDomain: allowSameDomain, allowSameDomain: allowSameDomain,
allowDomains: allowDomains, allowDomains: allowDomains,
denyDomains: denyDomains,
options: {}, options: {},
value: 0 value: 0
} }
@@ -281,6 +286,10 @@ Vue.component("http-firewall-checkpoint-referer-block", {
this.allowDomains = values this.allowDomains = values
this.change() this.change()
}, },
changeDenyDomains: function (values) {
this.denyDomains = values
this.change()
},
change: function () { change: function () {
this.vCheckpoint.options = [ this.vCheckpoint.options = [
{ {
@@ -294,7 +303,11 @@ Vue.component("http-firewall-checkpoint-referer-block", {
{ {
code: "allowDomains", code: "allowDomains",
value: this.allowDomains value: this.allowDomains
} },
{
code: "denyDomains",
value: this.denyDomains
},
] ]
} }
}, },
@@ -323,6 +336,13 @@ Vue.component("http-firewall-checkpoint-referer-block", {
<p class="comment">允许的来源域名列表,比如<code-label>example.com</code-label>、<code-label>*.example.com</code-label>。单个星号<code-label>*</code-label>表示允许所有域名。</p> <p class="comment">允许的来源域名列表,比如<code-label>example.com</code-label>、<code-label>*.example.com</code-label>。单个星号<code-label>*</code-label>表示允许所有域名。</p>
</td> </td>
</tr> </tr>
<tr>
<td>禁止的来源域名</td>
<td>
<values-box :values="denyDomains" @change="changeDenyDomains"></values-box>
<p class="comment">禁止的来源域名列表,比如<code-label>example.org</code-label>、<code-label>*.example.org</code-label>;除了这些禁止的来源域名外,其他域名都会被允许,除非限定了允许的来源域名。</p>
</td>
</tr>
</table> </table>
</div>` </div>`
}) })

View File

@@ -0,0 +1,44 @@
Vue.component("http-header-assistant", {
props: ["v-type", "v-value"],
mounted: function () {
let that = this
Tea.action("/servers/headers/options?type=" + this.vType)
.post()
.success(function (resp) {
that.allHeaders = resp.data.headers
})
},
data: function () {
return {
allHeaders: [],
matchedHeaders: [],
selectedHeaderName: ""
}
},
watch: {
vValue: function (v) {
if (v != this.selectedHeaderName) {
this.selectedHeaderName = ""
}
if (v.length == 0) {
this.matchedHeaders = []
return
}
this.matchedHeaders = this.allHeaders.filter(function (header) {
return teaweb.match(header, v)
}).slice(0, 5)
}
},
methods: {
select: function (header) {
this.$emit("select", header)
this.selectedHeaderName = header
}
},
template: `<span v-if="selectedHeaderName.length == 0">
<a href="" v-for="header in matchedHeaders" class="ui label basic tiny blue" style="font-weight: normal" @click.prevent="select(header)">{{header}}</a>
<span v-if="matchedHeaders.length > 0">&nbsp; &nbsp;</span>
</span>`
})

View File

@@ -100,10 +100,9 @@ Vue.component("http-host-redirect-box", {
<thead> <thead>
<tr> <tr>
<th style="width: 1em"></th> <th style="width: 1em"></th>
<th>跳转前URL</th> <th>跳转前</th>
<th style="width: 1em"></th> <th style="width: 1em"></th>
<th>跳转后URL</th> <th>跳转后</th>
<th>匹配模式</th>
<th>HTTP状态码</th> <th>HTTP状态码</th>
<th class="two wide">状态</th> <th class="two wide">状态</th>
<th class="two op">操作</th> <th class="two op">操作</th>
@@ -113,17 +112,48 @@ Vue.component("http-host-redirect-box", {
<tr> <tr>
<td style="text-align: center;"><i class="icon bars handle grey"></i> </td> <td style="text-align: center;"><i class="icon bars handle grey"></i> </td>
<td> <td>
{{redirect.beforeURL}} <div v-if="redirect.type == '' || redirect.type == 'url'">
{{redirect.beforeURL}}
<div style="margin-top: 0.4em">
<grey-label><strong>URL跳转</strong></grey-label>
<grey-label v-if="redirect.matchPrefix">匹配前缀</grey-label>
<grey-label v-if="redirect.matchRegexp">正则匹配</grey-label>
<grey-label v-if="!redirect.matchPrefix && !redirect.matchRegexp">精准匹配</grey-label>
</div>
</div>
<div v-if="redirect.type == 'domain'">
<span v-if="redirect.domainsAll">所有域名</span>
<span v-if="!redirect.domainsAll && redirect.domainsBefore != null">
<span v-if="redirect.domainsBefore.length == 1">{{redirect.domainsBefore[0]}}</span>
<span v-if="redirect.domainsBefore.length > 1">{{redirect.domainsBefore[0]}}等{{redirect.domainsBefore.length}}个域名</span>
</span>
<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'">
<span v-if="redirect.portsAll">所有端口</span>
<span v-if="!redirect.portsAll && redirect.portsBefore != null">
<span v-if="redirect.portsBefore.length <= 5">{{redirect.portsBefore.join(", ")}}</span>
<span v-if="redirect.portsBefore.length > 5">{{redirect.portsBefore.slice(0, 5).join(", ")}}等{{redirect.portsBefore.length}}个端口</span>
</span>
<div style="margin-top: 0.4em">
<grey-label><strong>端口跳转</strong></grey-label>
<grey-label v-if="redirect.portAfterScheme != null && redirect.portAfterScheme.length > 0">{{redirect.portAfterScheme}}</grey-label>
</div>
</div>
<div style="margin-top: 0.5em" v-if="redirect.conds != null && redirect.conds.groups != null && redirect.conds.groups.length > 0"> <div style="margin-top: 0.5em" v-if="redirect.conds != null && redirect.conds.groups != null && redirect.conds.groups.length > 0">
<span class="ui label text basic tiny">匹配条件</span> <grey-label>匹配条件</grey-label>
</div> </div>
</td> </td>
<td nowrap="">-&gt;</td> <td nowrap="">-&gt;</td>
<td>{{redirect.afterURL}}</td>
<td> <td>
<span v-if="redirect.matchPrefix">匹配前缀</span> <span v-if="redirect.type == '' || redirect.type == 'url'">{{redirect.afterURL}}</span>
<span v-if="redirect.matchRegexp">正则匹配</span> <span v-if="redirect.type == 'domain'">{{redirect.domainAfter}}</span>
<span v-if="!redirect.matchPrefix && !redirect.matchRegexp">精准匹配</span> <span v-if="redirect.type == 'port'">{{redirect.portAfter}}</span>
</td> </td>
<td> <td>
<span v-if="redirect.status > 0">{{redirect.status}}</span> <span v-if="redirect.status > 0">{{redirect.status}}</span>

View File

@@ -8,12 +8,16 @@ Vue.component("http-referers-config-box", {
isOn: false, isOn: false,
allowEmpty: true, allowEmpty: true,
allowSameDomain: true, allowSameDomain: true,
allowDomains: [] allowDomains: [],
denyDomains: []
} }
} }
if (config.allowDomains == null) { if (config.allowDomains == null) {
config.allowDomains = [] config.allowDomains = []
} }
if (config.denyDomains == null) {
config.denyDomains = []
}
return { return {
config: config config: config
} }
@@ -23,6 +27,8 @@ Vue.component("http-referers-config-box", {
return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn return ((!this.vIsLocation && !this.vIsGroup) || this.config.isPrior) && this.config.isOn
}, },
changeAllowDomains: function (domains) { changeAllowDomains: function (domains) {
},
changeDenyDomains: function (domains) {
} }
}, },
template: `<div> template: `<div>
@@ -59,10 +65,17 @@ Vue.component("http-referers-config-box", {
<tr> <tr>
<td>允许的来源域名</td> <td>允许的来源域名</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> <p class="comment">允许的其他来源域名列表,比如<code-label>example.com</code-label>、<code-label>*.example.com</code-label>。单个星号<code-label>*</code-label>表示允许所有域名。</p>
</td> </td>
</tr> </tr>
<tr>
<td>禁止的来源域名</td>
<td>
<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>
</tbody> </tbody>
</table> </table>
<div class="ui margin"></div> <div class="ui margin"></div>

View File

@@ -80,7 +80,7 @@ Vue.component("http-request-limit-config-box", {
<td>单IP最大并发连接数</td> <td>单IP最大并发连接数</td>
<td> <td>
<input type="text" maxlength="6" v-model="maxConnsPerIP"/> <input type="text" maxlength="6" v-model="maxConnsPerIP"/>
<p class="comment">单IP最大连接数统计单个IP总连接数时不区分服务超出此限制则响应用户<code-label>429</code-label>代码。为0表示不限制。<span v-if="maxConnsPerIP <= 3" class="red">当前设置的并发连接数过低可能会影响正常用户访问建议不小于3。</span></p> <p class="comment">单IP最大连接数统计单个IP总连接数时不区分服务超出此限制则响应用户<code-label>429</code-label>代码。为0表示不限制。<span v-if="maxConnsPerIP > 0 && maxConnsPerIP <= 3" class="red">当前设置的并发连接数过低可能会影响正常用户访问建议不小于3。</span></p>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@@ -18,7 +18,7 @@ Vue.component("http-stat-config-box", {
<prior-checkbox :v-config="stat" v-if="vIsLocation || vIsGroup" ></prior-checkbox> <prior-checkbox :v-config="stat" v-if="vIsLocation || vIsGroup" ></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || stat.isPrior"> <tbody v-show="(!vIsLocation && !vIsGroup) || stat.isPrior">
<tr> <tr>
<td class="title">是否开启统计</td> <td class="title">启统计</td>
<td> <td>
<div class="ui checkbox"> <div class="ui checkbox">
<input type="checkbox" v-model="stat.isOn"/> <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> <prior-checkbox :v-config="rootConfig" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || rootConfig.isPrior"> <tbody v-show="(!vIsLocation && !vIsGroup) || rootConfig.isPrior">
<tr> <tr>
<td class="title">是否开启静态资源分发</td> <td class="title">启静态资源分发</td>
<td> <td>
<div class="ui checkbox"> <div class="ui checkbox">
<input type="checkbox" v-model="rootConfig.isOn"/> <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> <prior-checkbox :v-config="config" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior"> <tbody v-show="(!vIsLocation && !vIsGroup) || config.isPrior">
<tr> <tr>
<td class="title">启用</td> <td class="title">启用WebP压缩</td>
<td> <td>
<div class="ui checkbox"> <div class="ui checkbox">
<input type="checkbox" value="1" v-model="config.isOn"/> <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> <prior-checkbox :v-config="websocketRef" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="((!vIsLocation && !vIsGroup) || websocketRef.isPrior)"> <tbody v-show="((!vIsLocation && !vIsGroup) || websocketRef.isPrior)">
<tr> <tr>
<td class="title">启用配置</td> <td class="title">启用Websocket</td>
<td> <td>
<div class="ui checkbox"> <div class="ui checkbox">
<input type="checkbox" v-model="websocketRef.isOn"/> <input type="checkbox" v-model="websocketRef.isOn"/>

View File

@@ -9,6 +9,7 @@ Vue.component("origin-list-box", {
methods: { methods: {
createPrimaryOrigin: function () { createPrimaryOrigin: function () {
teaweb.popup("/servers/server/settings/origins/addPopup?originType=primary&" + this.vParams, { teaweb.popup("/servers/server/settings/origins/addPopup?originType=primary&" + this.vParams, {
width: "45em",
height: "27em", height: "27em",
callback: function (resp) { callback: function (resp) {
teaweb.success("保存成功", function () { teaweb.success("保存成功", function () {
@@ -19,6 +20,7 @@ Vue.component("origin-list-box", {
}, },
createBackupOrigin: function () { createBackupOrigin: function () {
teaweb.popup("/servers/server/settings/origins/addPopup?originType=backup&" + this.vParams, { teaweb.popup("/servers/server/settings/origins/addPopup?originType=backup&" + this.vParams, {
width: "45em",
height: "27em", height: "27em",
callback: function (resp) { callback: function (resp) {
teaweb.success("保存成功", function () { teaweb.success("保存成功", function () {
@@ -29,6 +31,7 @@ Vue.component("origin-list-box", {
}, },
updateOrigin: function (originId, originType) { updateOrigin: function (originId, originType) {
teaweb.popup("/servers/server/settings/origins/updatePopup?originType=" + originType + "&" + this.vParams + "&originId=" + originId, { teaweb.popup("/servers/server/settings/origins/updatePopup?originType=" + originType + "&" + this.vParams + "&originId=" + originId, {
width: "45em",
height: "27em", height: "27em",
callback: function (resp) { callback: function (resp) {
teaweb.success("保存成功", function () { teaweb.success("保存成功", function () {

View File

@@ -416,7 +416,7 @@ Vue.component("ssl-config-box", {
<!-- HSTS --> <!-- HSTS -->
<tr v-show="vProtocol == 'https'"> <tr v-show="vProtocol == 'https'">
<td :class="{'color-border':hsts.isOn}">是否开启HSTS</td> <td :class="{'color-border':hsts.isOn}">开启HSTS</td>
<td> <td>
<div class="ui checkbox"> <div class="ui checkbox">
<input type="checkbox" name="hstsOn" v-model="hsts.isOn" value="1"/> <input type="checkbox" name="hstsOn" v-model="hsts.isOn" value="1"/>

View File

@@ -85,7 +85,6 @@ window.teaweb = {
return return
} }
if (typeof (element) == "string") { if (typeof (element) == "string") {
element = document.getElementById(element); element = document.getElementById(element);
} }
@@ -112,8 +111,9 @@ window.teaweb = {
}, },
reposition: !bottomLeft reposition: !bottomLeft
}) })
},
element.picker = picker
},
formatBytes: function (bytes) { formatBytes: function (bytes) {
bytes = Math.ceil(bytes); bytes = Math.ceil(bytes);
if (bytes < Math.pow(1024, 1)) { if (bytes < Math.pow(1024, 1)) {
@@ -136,6 +136,28 @@ window.teaweb = {
} }
return (Math.round(bytes * 100 / Math.pow(1024, 6)) / 100) + "EB"; return (Math.round(bytes * 100 / Math.pow(1024, 6)) / 100) + "EB";
}, },
formatBits: function (bits) {
bits = Math.ceil(bits);
if (bits < Math.pow(1024, 1)) {
return bits + "bps";
}
if (bits < Math.pow(1024, 2)) {
return (Math.round(bits * 10000 / Math.pow(1024, 1)) / 10000) + "Kbps";
}
if (bits < Math.pow(1024, 3)) {
return (Math.round(bits * 10000 / Math.pow(1024, 2)) / 10000) + "Mbps";
}
if (bits < Math.pow(1024, 4)) {
return (Math.round(bits * 10000 / Math.pow(1024, 3)) / 10000) + "Gbps";
}
if (bits < Math.pow(1024, 5)) {
return (Math.round(bits * 10000 / Math.pow(1024, 4)) / 10000) + "Tbps";
}
if (bits < Math.pow(1024, 6)) {
return (Math.round(bits * 10000 / Math.pow(1024, 5)) / 10000) + "Pbps";
}
return (Math.round(bits * 10000 / Math.pow(1024, 6)) / 10000) + "Ebps";
},
formatNumber: function (x) { formatNumber: function (x) {
if (x == null) { if (x == null) {
return "null" return "null"
@@ -205,6 +227,19 @@ window.teaweb = {
divider: divider divider: divider
} }
}, },
bitsAxis: function (stats, countFunc) {
let axis = this.bytesAxis(stats, countFunc)
let unit = axis.unit
if (unit == "B") {
unit = "bps"
} else {
unit += "bps"
}
return {
unit: unit,
divider: axis.divider
}
},
countAxis: function (stats, countFunc) { countAxis: function (stats, countFunc) {
let max = Math.max.apply(this, stats.map(countFunc)) let max = Math.max.apply(this, stats.map(countFunc))
let divider = 1 let divider = 1
@@ -559,12 +594,22 @@ window.teaweb = {
let max = options.max let max = options.max
let interval = options.interval let interval = options.interval
let left = options.left
if (typeof left != "number") {
left = 0
}
let right = options.right
if (typeof right != "number") {
right = 0
}
let chartBox = document.getElementById(chartId) let chartBox = document.getElementById(chartId)
if (chartBox == null) { if (chartBox == null) {
console.error("chart id '" + chartId + "' not found") console.error("chart id '" + chartId + "' not found")
return return
} }
let chart = this.initChart(chartBox) let chart = this.initChart(chartBox, options.cache)
let option = { let option = {
xAxis: { xAxis: {
data: values.map(xFunc), data: values.map(xFunc),
@@ -591,9 +636,9 @@ window.teaweb = {
} }
}, },
grid: { grid: {
left: 40, left: 40 + left,
top: 10, top: 10,
right: 20, right: 20 + right,
bottom: 20 bottom: 20
}, },
series: [ series: [
@@ -605,7 +650,8 @@ window.teaweb = {
color: this.DefaultChartColor color: this.DefaultChartColor
}, },
areaStyle: {}, areaStyle: {},
smooth: true smooth: true,
markLine: options.markLine
} }
], ],
animation: true, animation: true,
@@ -893,7 +939,11 @@ window.teaweb = {
return [40, -20] return [40, -20]
}, },
chartMap: {}, // dom id => chart chartMap: {}, // dom id => chart
initChart: function (dom) { initChart: function (dom, cache) {
if (typeof(cache) != "boolean") {
cache = true
}
let domId = dom.getAttribute("id") let domId = dom.getAttribute("id")
if (domId != null && domId.length > 0 && typeof (this.chartMap[domId]) == "object") { if (domId != null && domId.length > 0 && typeof (this.chartMap[domId]) == "object") {
return this.chartMap[domId] return this.chartMap[domId]
@@ -902,7 +952,9 @@ window.teaweb = {
window.addEventListener("resize", function () { window.addEventListener("resize", function () {
instance.resize() instance.resize()
}) })
this.chartMap[domId] = instance if (cache) {
this.chartMap[domId] = instance
}
return instance return instance
}, },
encodeHTML: function (s) { encodeHTML: function (s) {

View File

@@ -731,6 +731,9 @@ var.olive {
var.dash { var.dash {
border-bottom: 1px dashed grey; border-bottom: 1px dashed grey;
} }
var.normal {
font-style: normal;
}
/** checkbox **/ /** checkbox **/
.checkbox label a, .checkbox label a,
.checkbox label { .checkbox label {

File diff suppressed because one or more lines are too long

View File

@@ -133,7 +133,7 @@ Tea.context(function () {
this.showNodeTasks = function () { this.showNodeTasks = function () {
teaweb.popup("/clusters/tasks/listPopup", { teaweb.popup("/clusters/tasks/listPopup", {
height: "24em", height: "24em",
width: "50em" width: "54em"
}) })
} }
@@ -166,7 +166,7 @@ Tea.context(function () {
this.showDNSTasks = function () { this.showDNSTasks = function () {
teaweb.popup("/dns/tasks/listPopup", { teaweb.popup("/dns/tasks/listPopup", {
height: "24em", height: "24em",
width: "50em" width: "54em"
}) })
} }
}); });

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