Compare commits

...

38 Commits

Author SHA1 Message Date
刘祥超
bc1eb994f9 Update components.js 2022-01-16 20:04:11 +08:00
刘祥超
136f0fd4bd 源站支持客户端证书 2022-01-16 19:51:26 +08:00
刘祥超
021dc13ce9 CAPTCHA增加多个选项 2022-01-16 16:54:20 +08:00
刘祥超
925b71489d Update components.js 2022-01-14 10:46:02 +08:00
刘祥超
580ce9f8f5 可以在IP名单、访问日志中跳到对应的WAF规则集 2022-01-14 10:45:58 +08:00
刘祥超
2b3d8c062d 优化界面 2022-01-13 15:05:57 +08:00
刘祥超
41858e091a Update components.js 2022-01-13 10:03:54 +08:00
刘祥超
5b7eaf08ae 实现open file cache 2022-01-12 21:09:11 +08:00
刘祥超
7362722adb 优化代码 2022-01-11 16:02:27 +08:00
刘祥超
8198ea8819 可以使用集群搜索WAF策略、缓存策略 2022-01-11 15:46:47 +08:00
刘祥超
cb615f4cbc Update components.js 2022-01-11 15:28:50 +08:00
刘祥超
6623fd8362 优化交互 2022-01-11 15:26:43 +08:00
刘祥超
7f8be85116 运行日志可以使用集群、节点筛选 2022-01-11 14:59:19 +08:00
刘祥超
c61381441c 访问日志可以使用集群和节点搜索 2022-01-11 12:04:03 +08:00
刘祥超
b3cecdfea2 实现自动SYN Flood防护 2022-01-10 19:54:29 +08:00
刘祥超
66f582df58 WAF规则增加备注信息/其他界面优化 2022-01-10 10:28:23 +08:00
刘祥超
7f6f7e11ce 生成components.js 2022-01-09 20:13:18 +08:00
刘祥超
0bdda313da WAF策略增加是否使用本地防火墙设置 2022-01-09 17:05:51 +08:00
刘祥超
d5e851cff7 优化界面 2022-01-09 10:47:03 +08:00
刘祥超
92f1ec13f9 优化界面 2022-01-09 10:43:37 +08:00
刘祥超
5376006754 优化文字提示 2022-01-08 16:49:46 +08:00
刘祥超
361979411e IP名单增加未读数、按未读筛选等操作 2022-01-08 16:48:45 +08:00
刘祥超
3981308083 优化代码 2022-01-06 11:13:36 +08:00
刘祥超
facd5e14cc 改进文字 2022-01-05 20:13:22 +08:00
刘祥超
ecce92c528 Update components.js 2022-01-05 15:54:22 +08:00
刘祥超
7696940989 用户列表可以使用待审核、关键词搜索 2022-01-05 11:12:28 +08:00
刘祥超
941ff46c2c 实现用户注册/审核功能 2022-01-05 10:45:28 +08:00
刘祥超
94036073de 优化请求脚本配置交互 2022-01-03 21:48:48 +08:00
刘祥超
13216f481c 尝试自动在firewalld中开放端口 2022-01-03 16:28:27 +08:00
刘祥超
eb35df8720 改进服务访问日志、设置页在手机下的显示 2022-01-03 14:50:11 +08:00
刘祥超
bf500fe1a4 增加格式化数字函数 2022-01-03 12:04:09 +08:00
刘祥超
c173e86e62 创建服务时默认选中统计 2022-01-03 11:27:01 +08:00
刘祥超
d4cb148272 WAF动作中各个超时/有效秒数最大值从10位改成9位 2022-01-03 11:17:33 +08:00
刘祥超
ffc3f8544e 节点运行日志增加标签筛选 2022-01-03 11:08:59 +08:00
刘祥超
b326bfe63a 优化文字 2021-12-31 19:45:14 +08:00
刘祥超
12f3f47ef9 实现初版边缘脚本 2021-12-31 15:20:59 +08:00
刘祥超
45734747c1 修改版本为0.4.0 2021-12-31 15:19:44 +08:00
刘祥超
37933d814c 修改版本号0.3.8 2021-12-31 11:38:55 +08:00
128 changed files with 2112 additions and 283 deletions

View File

@@ -11,10 +11,6 @@ import (
var sharedAdminUIConfig *systemconfigs.AdminUIConfig = nil var sharedAdminUIConfig *systemconfigs.AdminUIConfig = nil
const (
AdminUISettingName = "adminUIConfig"
)
func LoadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) { func LoadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
locker.Lock() locker.Lock()
defer locker.Unlock() defer locker.Unlock()
@@ -41,7 +37,7 @@ func UpdateAdminUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
return err return err
} }
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{ _, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
Code: AdminUISettingName, Code: systemconfigs.SettingCodeAdminUIConfig,
ValueJSON: valueJSON, ValueJSON: valueJSON,
}) })
if err != nil { if err != nil {
@@ -52,7 +48,7 @@ func UpdateAdminUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
return nil return nil
} }
// 是否显示财务信息 // ShowFinance 是否显示财务信息
func ShowFinance() bool { func ShowFinance() bool {
config, _ := LoadAdminUIConfig() config, _ := LoadAdminUIConfig()
if config != nil && !config.ShowFinance { if config != nil && !config.ShowFinance {
@@ -70,7 +66,7 @@ func loadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
return nil, err return nil, err
} }
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{ resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
Code: AdminUISettingName, Code: systemconfigs.SettingCodeAdminUIConfig,
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -0,0 +1,27 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build community
// +build community
package nodelogutils
import (
"github.com/iwind/TeaGo/maps"
)
// FindCommonTags 查找常用的标签
func FindNodeCommonTags() []maps.Map {
return []maps.Map{
{
"name": "端口监听",
"code": "LISTENER",
},
{
"name": "WAF",
"code": "WAF",
},
{
"name": "访问日志",
"code": "ACCESS_LOG",
},
}
}

View File

@@ -2,6 +2,7 @@ package numberutils
import ( import (
"fmt" "fmt"
"github.com/iwind/TeaGo/types"
"strconv" "strconv"
) )
@@ -40,3 +41,16 @@ func FormatBytes(bytes int64) string {
return fmt.Sprintf("%.2fEB", float64(bytes)/float64(Pow1024(6))) return fmt.Sprintf("%.2fEB", float64(bytes)/float64(Pow1024(6)))
} }
} }
func FormatCount(count int64) string {
if count < 1000 {
return types.String(count)
}
if count < 1000 * 1000 {
return fmt.Sprintf("%.1fK", float32(count)/1000)
}
if count < 1000 * 1000 * 1000{
return fmt.Sprintf("%.1fM", float32(count)/1000/1000)
}
return fmt.Sprintf("%.1fB", float32(count)/1000/1000/1000)
}

View File

@@ -14,3 +14,13 @@ func TestFormatBytes(t *testing.T) {
t.Log(FormatBytes(1_000_000_000_000_000_000)) t.Log(FormatBytes(1_000_000_000_000_000_000))
t.Log(FormatBytes(9_000_000_000_000_000_000)) t.Log(FormatBytes(9_000_000_000_000_000_000))
} }
func TestFormatCount(t *testing.T) {
t.Log(FormatCount(1))
t.Log(FormatCount(1000))
t.Log(FormatCount(1500))
t.Log(FormatCount(1_000_000))
t.Log(FormatCount(1_500_000))
t.Log(FormatCount(1_000_000_000))
t.Log(FormatCount(1_500_000_000))
}

View File

@@ -1,6 +1,7 @@
package node package node
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/nodelogutils"
"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"
@@ -23,6 +24,7 @@ func (this *LogsAction) RunGet(params struct {
DayTo string DayTo string
Keyword string Keyword string
Level string Level string
Tag string
}) { }) {
// 初始化节点信息(用于菜单) // 初始化节点信息(用于菜单)
_, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId) _, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
@@ -31,11 +33,14 @@ func (this *LogsAction) RunGet(params struct {
return return
} }
this.Data["tags"] = nodelogutils.FindNodeCommonTags()
this.Data["nodeId"] = params.NodeId this.Data["nodeId"] = params.NodeId
this.Data["dayFrom"] = params.DayFrom this.Data["dayFrom"] = params.DayFrom
this.Data["dayTo"] = params.DayTo this.Data["dayTo"] = params.DayTo
this.Data["keyword"] = params.Keyword this.Data["keyword"] = params.Keyword
this.Data["level"] = params.Level this.Data["level"] = params.Level
this.Data["tag"] = params.Tag
countResp, err := this.RPC().NodeLogRPC().CountNodeLogs(this.AdminContext(), &pb.CountNodeLogsRequest{ countResp, err := this.RPC().NodeLogRPC().CountNodeLogs(this.AdminContext(), &pb.CountNodeLogsRequest{
Role: "node", Role: "node",
@@ -44,6 +49,7 @@ func (this *LogsAction) RunGet(params struct {
DayTo: params.DayTo, DayTo: params.DayTo,
Keyword: params.Keyword, Keyword: params.Keyword,
Level: params.Level, Level: params.Level,
Tag: params.Tag,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
@@ -59,6 +65,7 @@ func (this *LogsAction) RunGet(params struct {
DayTo: params.DayTo, DayTo: params.DayTo,
Keyword: params.Keyword, Keyword: params.Keyword,
Level: params.Level, Level: params.Level,
Tag: params.Tag,
Offset: page.Offset, Offset: page.Offset,
Size: page.Size, Size: page.Size,
}) })

View File

@@ -17,7 +17,7 @@ type IndexAction struct {
} }
func (this *IndexAction) Init() { func (this *IndexAction) Init() {
this.Nav("", "", "update") this.Nav("", "node", "update")
this.SecondMenu("cache") this.SecondMenu("cache")
} }

View File

@@ -16,7 +16,7 @@ type IndexAction struct {
} }
func (this *IndexAction) Init() { func (this *IndexAction) Init() {
this.Nav("", "", "update") this.Nav("", "node", "update")
this.SecondMenu("dns") this.SecondMenu("dns")
} }

View File

@@ -17,7 +17,7 @@ type IndexAction struct {
} }
func (this *IndexAction) Init() { func (this *IndexAction) Init() {
this.Nav("", "", "update") this.Nav("", "node", "update")
this.SecondMenu("ssh") this.SecondMenu("ssh")
} }

View File

@@ -15,7 +15,7 @@ type IndexAction struct {
} }
func (this *IndexAction) Init() { func (this *IndexAction) Init() {
this.Nav("", "", "update") this.Nav("", "node", "update")
this.SecondMenu("system") this.SecondMenu("system")
} }

View File

@@ -15,7 +15,7 @@ type IndexAction struct {
} }
func (this *IndexAction) Init() { func (this *IndexAction) Init() {
this.Nav("", "", "update") this.Nav("", "node", "update")
this.SecondMenu("threshold") this.SecondMenu("threshold")
} }

View File

@@ -72,6 +72,7 @@ func (this *IndexAction) RunGet(params struct {
"timeZone": cluster.TimeZone, "timeZone": cluster.TimeZone,
"nodeMaxThreads": cluster.NodeMaxThreads, "nodeMaxThreads": cluster.NodeMaxThreads,
"nodeTCPMaxConnections": cluster.NodeTCPMaxConnections, "nodeTCPMaxConnections": cluster.NodeTCPMaxConnections,
"autoOpenPorts": cluster.AutoOpenPorts,
} }
// 默认值 // 默认值
@@ -92,6 +93,7 @@ func (this *IndexAction) RunPost(params struct {
TimeZone string TimeZone string
NodeMaxThreads int32 NodeMaxThreads int32
NodeTCPMaxConnections int32 NodeTCPMaxConnections int32
AutoOpenPorts bool
Must *actions.Must Must *actions.Must
}) { }) {
@@ -117,6 +119,7 @@ func (this *IndexAction) RunPost(params struct {
TimeZone: params.TimeZone, TimeZone: params.TimeZone,
NodeMaxThreads: params.NodeMaxThreads, NodeMaxThreads: params.NodeMaxThreads,
NodeTCPMaxConnections: params.NodeTCPMaxConnections, NodeTCPMaxConnections: params.NodeTCPMaxConnections,
AutoOpenPorts: params.AutoOpenPorts,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -21,8 +21,8 @@ func init() {
EndHelpers(). EndHelpers().
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)). Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)).
Post("/options", new(OptionsAction)). Post("/options", new(OptionsAction)).
Post("/nodeOptions", new(NodeOptionsAction)).
GetPost("/selectPopup", new(SelectPopupAction)). GetPost("/selectPopup", new(SelectPopupAction)).
EndAll() EndAll()
}) })
} }

View File

@@ -1,6 +1,7 @@
package logs package logs
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/nodelogutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"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"
@@ -21,17 +22,26 @@ func (this *IndexAction) Init() {
} }
func (this *IndexAction) RunGet(params struct { func (this *IndexAction) RunGet(params struct {
DayFrom string DayFrom string
DayTo string DayTo string
Keyword string Keyword string
Level string Level string
Type string Type string
Tag string
ClusterId int64
NodeId int64
}) { }) {
this.Data["dayFrom"] = params.DayFrom this.Data["dayFrom"] = params.DayFrom
this.Data["dayTo"] = params.DayTo this.Data["dayTo"] = params.DayTo
this.Data["keyword"] = params.Keyword this.Data["keyword"] = params.Keyword
this.Data["level"] = params.Level this.Data["level"] = params.Level
this.Data["type"] = params.Type this.Data["type"] = params.Type
this.Data["tag"] = params.Tag
this.Data["clusterId"] = params.ClusterId
this.Data["nodeId"] = params.NodeId
// 常见标签
this.Data["tags"] = nodelogutils.FindNodeCommonTags()
// 未读数量 // 未读数量
countUnreadResp, err := this.RPC().NodeLogRPC().CountNodeLogs(this.AdminContext(), &pb.CountNodeLogsRequest{ countUnreadResp, err := this.RPC().NodeLogRPC().CountNodeLogs(this.AdminContext(), &pb.CountNodeLogsRequest{
@@ -46,13 +56,15 @@ func (this *IndexAction) RunGet(params struct {
// 日志数量 // 日志数量
countResp, err := this.RPC().NodeLogRPC().CountNodeLogs(this.AdminContext(), &pb.CountNodeLogsRequest{ countResp, err := this.RPC().NodeLogRPC().CountNodeLogs(this.AdminContext(), &pb.CountNodeLogsRequest{
NodeId: 0, NodeClusterId: params.ClusterId,
Role: nodeconfigs.NodeRoleNode, NodeId: params.NodeId,
DayFrom: params.DayFrom, Role: nodeconfigs.NodeRoleNode,
DayTo: params.DayTo, DayFrom: params.DayFrom,
Keyword: params.Keyword, DayTo: params.DayTo,
Level: params.Level, Keyword: params.Keyword,
IsUnread: params.Type == "unread", Level: params.Level,
IsUnread: params.Type == "unread",
Tag: params.Tag,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
@@ -63,15 +75,17 @@ func (this *IndexAction) RunGet(params struct {
this.Data["page"] = page.AsHTML() this.Data["page"] = page.AsHTML()
logsResp, err := this.RPC().NodeLogRPC().ListNodeLogs(this.AdminContext(), &pb.ListNodeLogsRequest{ logsResp, err := this.RPC().NodeLogRPC().ListNodeLogs(this.AdminContext(), &pb.ListNodeLogsRequest{
NodeId: 0, NodeClusterId: params.ClusterId,
Role: nodeconfigs.NodeRoleNode, NodeId: params.NodeId,
DayFrom: params.DayFrom, Role: nodeconfigs.NodeRoleNode,
DayTo: params.DayTo, DayFrom: params.DayFrom,
Keyword: params.Keyword, DayTo: params.DayTo,
Level: params.Level, Keyword: params.Keyword,
IsUnread: params.Type == "unread", Level: params.Level,
Offset: page.Offset, IsUnread: params.Type == "unread",
Size: page.Size, Tag: params.Tag,
Offset: page.Offset,
Size: page.Size,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -0,0 +1,34 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package clusters
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type NodeOptionsAction struct {
actionutils.ParentAction
}
func (this *NodeOptionsAction) RunPost(params struct {
ClusterId int64
}) {
resp, err := this.RPC().NodeRPC().FindAllEnabledNodesWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var nodeMaps = []maps.Map{}
for _, node := range resp.Nodes {
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"name": node.Name,
})
}
this.Data["nodes"] = nodeMaps
this.Success()
}

View File

@@ -31,6 +31,7 @@ func (this *CreatePopupAction) RunPost(params struct {
// file // file
FileDir string FileDir string
FileMemoryCapacityJSON []byte FileMemoryCapacityJSON []byte
FileOpenFileCacheMax int
CapacityJSON []byte CapacityJSON []byte
MaxSizeJSON []byte MaxSizeJSON []byte
@@ -62,15 +63,23 @@ func (this *CreatePopupAction) RunPost(params struct {
} }
} }
var openFileCacheConfig *serverconfigs.OpenFileCacheConfig
if params.FileOpenFileCacheMax > 0 {
openFileCacheConfig = &serverconfigs.OpenFileCacheConfig{
IsOn: true,
Max: params.FileOpenFileCacheMax,
}
}
options = &serverconfigs.HTTPFileCacheStorage{ options = &serverconfigs.HTTPFileCacheStorage{
Dir: params.FileDir, Dir: params.FileDir,
MemoryPolicy: &serverconfigs.HTTPCachePolicy{ MemoryPolicy: &serverconfigs.HTTPCachePolicy{
Capacity: memoryCapacity, Capacity: memoryCapacity,
}, },
OpenFileCache: openFileCacheConfig,
} }
case serverconfigs.CachePolicyStorageMemory: case serverconfigs.CachePolicyStorageMemory:
options = &serverconfigs.HTTPMemoryCacheStorage{ options = &serverconfigs.HTTPMemoryCacheStorage{}
}
default: default:
this.Fail("请选择正确的缓存类型") this.Fail("请选择正确的缓存类型")
} }

View File

@@ -17,12 +17,15 @@ func (this *IndexAction) Init() {
} }
func (this *IndexAction) RunGet(params struct { func (this *IndexAction) RunGet(params struct {
Keyword string ClusterId int64
Keyword string
}) { }) {
this.Data["keyword"] = params.Keyword this.Data["keyword"] = params.Keyword
this.Data["clusterId"] = params.ClusterId
countResp, err := this.RPC().HTTPCachePolicyRPC().CountAllEnabledHTTPCachePolicies(this.AdminContext(), &pb.CountAllEnabledHTTPCachePoliciesRequest{ countResp, err := this.RPC().HTTPCachePolicyRPC().CountAllEnabledHTTPCachePolicies(this.AdminContext(), &pb.CountAllEnabledHTTPCachePoliciesRequest{
Keyword: params.Keyword, NodeClusterId: params.ClusterId,
Keyword: params.Keyword,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
@@ -33,9 +36,10 @@ func (this *IndexAction) RunGet(params struct {
this.Data["page"] = page.AsHTML() this.Data["page"] = page.AsHTML()
listResp, err := this.RPC().HTTPCachePolicyRPC().ListEnabledHTTPCachePolicies(this.AdminContext(), &pb.ListEnabledHTTPCachePoliciesRequest{ listResp, err := this.RPC().HTTPCachePolicyRPC().ListEnabledHTTPCachePolicies(this.AdminContext(), &pb.ListEnabledHTTPCachePoliciesRequest{
Keyword: params.Keyword, Keyword: params.Keyword,
Offset: page.Offset, NodeClusterId: params.ClusterId,
Size: page.Size, Offset: page.Offset,
Size: page.Size,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -55,6 +55,7 @@ func (this *UpdateAction) RunPost(params struct {
// file // file
FileDir string FileDir string
FileMemoryCapacityJSON []byte FileMemoryCapacityJSON []byte
FileOpenFileCacheMax int
CapacityJSON []byte CapacityJSON []byte
MaxSizeJSON []byte MaxSizeJSON []byte
@@ -91,15 +92,23 @@ func (this *UpdateAction) RunPost(params struct {
} }
} }
var openFileCacheConfig *serverconfigs.OpenFileCacheConfig
if params.FileOpenFileCacheMax > 0 {
openFileCacheConfig = &serverconfigs.OpenFileCacheConfig{
IsOn: true,
Max: params.FileOpenFileCacheMax,
}
}
options = &serverconfigs.HTTPFileCacheStorage{ options = &serverconfigs.HTTPFileCacheStorage{
Dir: params.FileDir, Dir: params.FileDir,
MemoryPolicy: &serverconfigs.HTTPCachePolicy{ MemoryPolicy: &serverconfigs.HTTPCachePolicy{
Capacity: memoryCapacity, Capacity: memoryCapacity,
}, },
OpenFileCache: openFileCacheConfig,
} }
case serverconfigs.CachePolicyStorageMemory: case serverconfigs.CachePolicyStorageMemory:
options = &serverconfigs.HTTPMemoryCacheStorage{ options = &serverconfigs.HTTPMemoryCacheStorage{}
}
default: default:
this.Fail("请选择正确的缓存类型") this.Fail("请选择正确的缓存类型")
} }

View File

@@ -61,6 +61,7 @@ func (this *CreateRulePopupAction) RunPost(params struct {
OptionsJSON []byte OptionsJSON []byte
Value string Value string
Case bool Case bool
Description string
Must *actions.Must Must *actions.Must
}) { }) {
@@ -91,6 +92,7 @@ func (this *CreateRulePopupAction) RunPost(params struct {
rule.Operator = params.Operator rule.Operator = params.Operator
rule.Value = params.Value rule.Value = params.Value
rule.IsCaseInsensitive = params.Case rule.IsCaseInsensitive = params.Case
rule.Description = params.Description
if len(params.OptionsJSON) > 0 { if len(params.OptionsJSON) > 0 {
options := []maps.Map{} options := []maps.Map{}

View File

@@ -87,6 +87,7 @@ func (this *GroupAction) RunGet(params struct {
"operator": rule.Operator, "operator": rule.Operator,
"value": rule.Value, "value": rule.Value,
"isCaseInsensitive": rule.IsCaseInsensitive, "isCaseInsensitive": rule.IsCaseInsensitive,
"description": rule.Description,
"isComposed": firewallconfigs.CheckCheckpointIsComposed(rule.Prefix()), "isComposed": firewallconfigs.CheckCheckpointIsComposed(rule.Prefix()),
"checkpointOptions": rule.CheckpointOptions, "checkpointOptions": rule.CheckpointOptions,
"err": errString, "err": errString,

View File

@@ -17,12 +17,15 @@ func (this *IndexAction) Init() {
} }
func (this *IndexAction) RunGet(params struct { func (this *IndexAction) RunGet(params struct {
Keyword string Keyword string
ClusterId int64
}) { }) {
this.Data["keyword"] = params.Keyword this.Data["keyword"] = params.Keyword
this.Data["clusterId"] = params.ClusterId
countResp, err := this.RPC().HTTPFirewallPolicyRPC().CountAllEnabledHTTPFirewallPolicies(this.AdminContext(), &pb.CountAllEnabledHTTPFirewallPoliciesRequest{ countResp, err := this.RPC().HTTPFirewallPolicyRPC().CountAllEnabledHTTPFirewallPolicies(this.AdminContext(), &pb.CountAllEnabledHTTPFirewallPoliciesRequest{
Keyword: params.Keyword, NodeClusterId: params.ClusterId,
Keyword: params.Keyword,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
@@ -32,9 +35,10 @@ func (this *IndexAction) RunGet(params struct {
page := this.NewPage(count) page := this.NewPage(count)
listResp, err := this.RPC().HTTPFirewallPolicyRPC().ListEnabledHTTPFirewallPolicies(this.AdminContext(), &pb.ListEnabledHTTPFirewallPoliciesRequest{ listResp, err := this.RPC().HTTPFirewallPolicyRPC().ListEnabledHTTPFirewallPolicies(this.AdminContext(), &pb.ListEnabledHTTPFirewallPoliciesRequest{
Keyword: params.Keyword, NodeClusterId: params.ClusterId,
Offset: page.Offset, Keyword: params.Keyword,
Size: page.Size, Offset: page.Offset,
Size: page.Size,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -47,7 +47,7 @@ func (this *IndexAction) RunGet(params struct {
return return
} }
countryMaps := []maps.Map{} countryMaps := []maps.Map{}
for _, country := range countriesResp.Countries { for _, country := range countriesResp.RegionCountries {
countryMaps = append(countryMaps, maps.Map{ countryMaps = append(countryMaps, maps.Map{
"id": country.Id, "id": country.Id,
"name": country.Name, "name": country.Name,

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"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"
) )
type ListsAction struct { type ListsAction struct {
@@ -109,6 +110,7 @@ func (this *ListsAction) RunGet(params struct {
"sourceGroup": sourceGroupMap, "sourceGroup": sourceGroupMap,
"sourceSet": sourceSetMap, "sourceSet": sourceSetMap,
"sourceServer": sourceServerMap, "sourceServer": sourceServerMap,
"lifeSeconds": item.ExpiredAt - time.Now().Unix(),
}) })
} }
this.Data["items"] = itemMaps this.Data["items"] = itemMaps

View File

@@ -43,14 +43,14 @@ func (this *ProvincesAction) RunGet(params struct {
} }
provincesResp, err := this.RPC().RegionProvinceRPC().FindAllEnabledRegionProvincesWithCountryId(this.AdminContext(), &pb.FindAllEnabledRegionProvincesWithCountryIdRequest{ provincesResp, err := this.RPC().RegionProvinceRPC().FindAllEnabledRegionProvincesWithCountryId(this.AdminContext(), &pb.FindAllEnabledRegionProvincesWithCountryIdRequest{
CountryId: int64(ChinaCountryId), RegionCountryId: int64(ChinaCountryId),
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
provinceMaps := []maps.Map{} provinceMaps := []maps.Map{}
for _, province := range provincesResp.Provinces { for _, province := range provincesResp.RegionProvinces {
provinceMaps = append(provinceMaps, maps.Map{ provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id, "id": province.Id,
"name": province.Name, "name": province.Name,

View File

@@ -84,15 +84,18 @@ func (this *PolicyAction) RunGet(params struct {
if len(firewallPolicy.Mode) == 0 { if len(firewallPolicy.Mode) == 0 {
firewallPolicy.Mode = firewallconfigs.FirewallModeDefend firewallPolicy.Mode = firewallconfigs.FirewallModeDefend
} }
this.Data["firewallPolicy"] = maps.Map{ this.Data["firewallPolicy"] = maps.Map{
"id": firewallPolicy.Id, "id": firewallPolicy.Id,
"name": firewallPolicy.Name, "name": firewallPolicy.Name,
"isOn": firewallPolicy.IsOn, "isOn": firewallPolicy.IsOn,
"description": firewallPolicy.Description, "description": firewallPolicy.Description,
"mode": firewallPolicy.Mode, "mode": firewallPolicy.Mode,
"modeInfo": firewallconfigs.FindFirewallMode(firewallPolicy.Mode), "modeInfo": firewallconfigs.FindFirewallMode(firewallPolicy.Mode),
"groups": internalGroups, "groups": internalGroups,
"blockOptions": firewallPolicy.BlockOptions, "blockOptions": firewallPolicy.BlockOptions,
"useLocalFirewall": firewallPolicy.UseLocalFirewall,
"synFlood": firewallPolicy.SYNFlood,
} }
// 正在使用此策略的集群 // 正在使用此策略的集群

View File

@@ -48,13 +48,25 @@ func (this *UpdateAction) RunGet(params struct {
} }
this.Data["modes"] = firewallconfigs.FindAllFirewallModes() this.Data["modes"] = firewallconfigs.FindAllFirewallModes()
// syn flood
if firewallPolicy.SYNFlood == nil {
firewallPolicy.SYNFlood = &firewallconfigs.SYNFloodConfig{
IsOn: false,
MinAttempts: 10,
TimeoutSeconds: 600,
IgnoreLocal: true,
}
}
this.Data["firewallPolicy"] = maps.Map{ this.Data["firewallPolicy"] = maps.Map{
"id": firewallPolicy.Id, "id": firewallPolicy.Id,
"name": firewallPolicy.Name, "name": firewallPolicy.Name,
"description": firewallPolicy.Description, "description": firewallPolicy.Description,
"isOn": firewallPolicy.IsOn, "isOn": firewallPolicy.IsOn,
"mode": firewallPolicy.Mode, "mode": firewallPolicy.Mode,
"blockOptions": firewallPolicy.BlockOptions, "blockOptions": firewallPolicy.BlockOptions,
"useLocalFirewall": firewallPolicy.UseLocalFirewall,
"synFloodConfig": firewallPolicy.SYNFlood,
} }
// 预置分组 // 预置分组
@@ -87,6 +99,8 @@ func (this *UpdateAction) RunPost(params struct {
Description string Description string
IsOn bool IsOn bool
Mode string Mode string
UseLocalFirewall bool
SynFloodJSON []byte
Must *actions.Must Must *actions.Must
}) { }) {
@@ -112,6 +126,8 @@ func (this *UpdateAction) RunPost(params struct {
FirewallGroupCodes: params.GroupCodes, FirewallGroupCodes: params.GroupCodes,
BlockOptionsJSON: params.BlockOptionsJSON, BlockOptionsJSON: params.BlockOptionsJSON,
Mode: params.Mode, Mode: params.Mode,
UseLocalFirewall: params.UseLocalFirewall,
SynFloodJSON: params.SynFloodJSON,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -42,7 +42,7 @@ func (this *CreatePopupAction) RunPost(params struct {
} }
// 创建日志 // 创建日志
defer this.CreateLog(oplogs.LevelInfo, "创建代理服务分组 %d", createResp.ServerGroupId) defer this.CreateLog(oplogs.LevelInfo, "创建服务分组 %d", createResp.ServerGroupId)
this.Success() this.Success()
} }

View File

@@ -63,6 +63,7 @@ func (this *IndexAction) RunGet(params struct {
"name": originConfig.Name, "name": originConfig.Name,
"isOn": originConfig.IsOn, "isOn": originConfig.IsOn,
"domains": originConfig.Domains, "domains": originConfig.Domains,
"hasCert": originConfig.Cert != nil,
} }
primaryOriginMaps = append(primaryOriginMaps, m) primaryOriginMaps = append(primaryOriginMaps, m)
} }
@@ -77,6 +78,7 @@ func (this *IndexAction) RunGet(params struct {
"name": originConfig.Name, "name": originConfig.Name,
"isOn": originConfig.IsOn, "isOn": originConfig.IsOn,
"domains": originConfig.Domains, "domains": originConfig.Domains,
"hasCert": originConfig.Cert != nil,
} }
backupOriginMaps = append(backupOriginMaps, m) backupOriginMaps = append(backupOriginMaps, m)
} }

View File

@@ -63,6 +63,7 @@ func (this *IndexAction) RunGet(params struct {
"name": originConfig.Name, "name": originConfig.Name,
"isOn": originConfig.IsOn, "isOn": originConfig.IsOn,
"domains": originConfig.Domains, "domains": originConfig.Domains,
"hasCert": originConfig.Cert != nil,
} }
primaryOriginMaps = append(primaryOriginMaps, m) primaryOriginMaps = append(primaryOriginMaps, m)
} }
@@ -77,6 +78,7 @@ func (this *IndexAction) RunGet(params struct {
"name": originConfig.Name, "name": originConfig.Name,
"isOn": originConfig.IsOn, "isOn": originConfig.IsOn,
"domains": originConfig.Domains, "domains": originConfig.Domains,
"hasCert": originConfig.Cert != nil,
} }
backupOriginMaps = append(backupOriginMaps, m) backupOriginMaps = append(backupOriginMaps, m)
} }

View File

@@ -63,6 +63,7 @@ func (this *IndexAction) RunGet(params struct {
"name": originConfig.Name, "name": originConfig.Name,
"isOn": originConfig.IsOn, "isOn": originConfig.IsOn,
"domains": originConfig.Domains, "domains": originConfig.Domains,
"hasCert": originConfig.Cert != nil,
} }
primaryOriginMaps = append(primaryOriginMaps, m) primaryOriginMaps = append(primaryOriginMaps, m)
} }
@@ -77,6 +78,7 @@ func (this *IndexAction) RunGet(params struct {
"name": originConfig.Name, "name": originConfig.Name,
"isOn": originConfig.IsOn, "isOn": originConfig.IsOn,
"domains": originConfig.Domains, "domains": originConfig.Domains,
"hasCert": originConfig.Cert != nil,
} }
backupOriginMaps = append(backupOriginMaps, m) backupOriginMaps = append(backupOriginMaps, m)
} }

View File

@@ -85,6 +85,7 @@ func (this *GroupAction) RunGet(params struct {
"operator": rule.Operator, "operator": rule.Operator,
"value": rule.Value, "value": rule.Value,
"isCaseInsensitive": rule.IsCaseInsensitive, "isCaseInsensitive": rule.IsCaseInsensitive,
"description": rule.Description,
"isComposed": firewallconfigs.CheckCheckpointIsComposed(rule.Prefix()), "isComposed": firewallconfigs.CheckCheckpointIsComposed(rule.Prefix()),
"checkpointOptions": rule.CheckpointOptions, "checkpointOptions": rule.CheckpointOptions,
"err": errString, "err": errString,

View File

@@ -113,6 +113,7 @@ func (this *AllowListAction) RunGet(params struct {
"ipTo": item.IpTo, "ipTo": item.IpTo,
"createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt), "createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt),
"expiredTime": expiredTime, "expiredTime": expiredTime,
"lifeSeconds": item.ExpiredAt - time.Now().Unix(),
"reason": item.Reason, "reason": item.Reason,
"type": item.Type, "type": item.Type,
"isExpired": item.ExpiredAt > 0 && item.ExpiredAt < time.Now().Unix(), "isExpired": item.ExpiredAt > 0 && item.ExpiredAt < time.Now().Unix(),

View File

@@ -113,6 +113,7 @@ func (this *DenyListAction) RunGet(params struct {
"ipTo": item.IpTo, "ipTo": item.IpTo,
"createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt), "createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt),
"expiredTime": expiredTime, "expiredTime": expiredTime,
"lifeSeconds": item.ExpiredAt - time.Now().Unix(),
"reason": item.Reason, "reason": item.Reason,
"type": item.Type, "type": item.Type,
"isExpired": item.ExpiredAt > 0 && item.ExpiredAt < time.Now().Unix(), "isExpired": item.ExpiredAt > 0 && item.ExpiredAt < time.Now().Unix(),

View File

@@ -14,7 +14,7 @@ func (this *SortAction) RunPost(params struct {
GroupIds []int64 GroupIds []int64
}) { }) {
// 创建日志 // 创建日志
defer this.CreateLog(oplogs.LevelInfo, "修改代理分组排序") defer this.CreateLog(oplogs.LevelInfo, "修改服务分组排序")
_, err := this.RPC().ServerGroupRPC().UpdateServerGroupOrders(this.AdminContext(), &pb.UpdateServerGroupOrdersRequest{ServerGroupIds: params.GroupIds}) _, err := this.RPC().ServerGroupRPC().UpdateServerGroupOrders(this.AdminContext(), &pb.UpdateServerGroupOrdersRequest{ServerGroupIds: params.GroupIds})
if err != nil { if err != nil {

View File

@@ -266,7 +266,7 @@ func (this *IndexAction) RunGet(params struct {
Role: nodeconfigs.NodeRoleNode, Role: nodeconfigs.NodeRoleNode,
Offset: 0, Offset: 0,
Size: 20, Size: 20,
Level: "", Level: "error,success,warning",
FixedState: int32(configutils.BoolStateNo), FixedState: int32(configutils.BoolStateNo),
AllServers: true, AllServers: true,
}) })

View File

@@ -10,6 +10,7 @@ import (
func init() { func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) { TeaGo.BeforeStart(func(server *TeaGo.Server) {
server. server.
Data("teaMenu", "servers").
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)). Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
Helper(NewHelper()). Helper(NewHelper()).
Prefix("/servers"). Prefix("/servers").

View File

@@ -22,14 +22,28 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct { func (this *IndexAction) RunGet(params struct {
Ip string Ip string
GlobalOnly bool GlobalOnly bool
Unread bool
}) { }) {
this.Data["type"] = "" this.Data["type"] = ""
this.Data["ip"] = params.Ip this.Data["ip"] = params.Ip
this.Data["globalOnly"] = params.GlobalOnly this.Data["globalOnly"] = params.GlobalOnly
this.Data["unread"] = params.Unread
countUnreadResp, err := this.RPC().IPItemRPC().CountAllEnabledIPItems(this.AdminContext(), &pb.CountAllEnabledIPItemsRequest{
Ip: params.Ip,
GlobalOnly: params.GlobalOnly,
Unread: true,
})
if err != nil {
this.ErrorPage(err)
return
}
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, Ip: params.Ip,
GlobalOnly: params.GlobalOnly, GlobalOnly: params.GlobalOnly,
Unread: params.Unread,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
@@ -42,6 +56,7 @@ func (this *IndexAction) RunGet(params struct {
itemsResp, err := this.RPC().IPItemRPC().ListAllEnabledIPItems(this.AdminContext(), &pb.ListAllEnabledIPItemsRequest{ itemsResp, err := this.RPC().IPItemRPC().ListAllEnabledIPItems(this.AdminContext(), &pb.ListAllEnabledIPItemsRequest{
Ip: params.Ip, Ip: params.Ip,
GlobalOnly: params.GlobalOnly, GlobalOnly: params.GlobalOnly,
Unread: params.Unread,
Offset: page.Offset, Offset: page.Offset,
Size: page.Size, Size: page.Size,
}) })
@@ -118,6 +133,16 @@ func (this *IndexAction) RunGet(params struct {
} }
} }
// node
var sourceNodeMap = maps.Map{"id": 0}
if item.SourceNode != nil && item.SourceNode.NodeCluster != nil {
sourceNodeMap = maps.Map{
"id": item.SourceNode.Id,
"name": item.SourceNode.Name,
"clusterId": item.SourceNode.NodeCluster.Id,
}
}
itemMaps = append(itemMaps, maps.Map{ itemMaps = append(itemMaps, maps.Map{
"id": item.Id, "id": item.Id,
"ipFrom": item.IpFrom, "ipFrom": item.IpFrom,
@@ -127,11 +152,14 @@ func (this *IndexAction) RunGet(params struct {
"expiredTime": expiredTime, "expiredTime": expiredTime,
"reason": item.Reason, "reason": item.Reason,
"type": item.Type, "type": item.Type,
"isRead": item.IsRead,
"lifeSeconds": item.ExpiredAt - time.Now().Unix(),
"eventLevelName": firewallconfigs.FindFirewallEventLevelName(item.EventLevel), "eventLevelName": firewallconfigs.FindFirewallEventLevelName(item.EventLevel),
"sourcePolicy": sourcePolicyMap, "sourcePolicy": sourcePolicyMap,
"sourceGroup": sourceGroupMap, "sourceGroup": sourceGroupMap,
"sourceSet": sourceSetMap, "sourceSet": sourceSetMap,
"sourceServer": sourceServerMap, "sourceServer": sourceServerMap,
"sourceNode": sourceNodeMap,
"list": listMap, "list": listMap,
"policy": policyMap, "policy": policyMap,
}) })

View File

@@ -32,6 +32,7 @@ func init() {
GetPost("/updateIPPopup", new(UpdateIPPopupAction)). GetPost("/updateIPPopup", new(UpdateIPPopupAction)).
Post("/deleteIP", new(DeleteIPAction)). Post("/deleteIP", new(DeleteIPAction)).
Get("/accessLogsPopup", new(AccessLogsPopupAction)). Get("/accessLogsPopup", new(AccessLogsPopupAction)).
Post("/readAll", new(ReadAllAction)).
// 防火墙 // 防火墙
GetPost("/bindHTTPFirewallPopup", new(BindHTTPFirewallPopupAction)). GetPost("/bindHTTPFirewallPopup", new(BindHTTPFirewallPopupAction)).

View File

@@ -8,6 +8,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"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"
) )
type ItemsAction struct { type ItemsAction struct {
@@ -112,6 +113,7 @@ func (this *ItemsAction) RunGet(params struct {
"sourceGroup": sourceGroupMap, "sourceGroup": sourceGroupMap,
"sourceSet": sourceSetMap, "sourceSet": sourceSetMap,
"sourceServer": sourceServerMap, "sourceServer": sourceServerMap,
"lifeSeconds": item.ExpiredAt - time.Now().Unix(),
}) })
} }
this.Data["items"] = itemMaps this.Data["items"] = itemMaps

View File

@@ -0,0 +1,24 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type ReadAllAction struct {
actionutils.ParentAction
}
func (this *ReadAllAction) RunPost(params struct{}) {
defer this.CreateLogInfo("将IP名单置为已读")
_, err := this.RPC().IPItemRPC().UpdateIPItemsRead(this.AdminContext(), &pb.UpdateIPItemsReadRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -22,12 +22,14 @@ func (this *IndexAction) Init() {
} }
func (this *IndexAction) RunGet(params struct { func (this *IndexAction) RunGet(params struct {
Day string ClusterId int64
Keyword string NodeId int64
Ip string Day string
Domain string Keyword string
HasError int Ip string
HasWAF int Domain string
HasError int
HasWAF int
RequestId string RequestId string
ServerId int64 ServerId int64
@@ -38,6 +40,8 @@ func (this *IndexAction) RunGet(params struct {
params.Day = timeutil.Format("Y-m-d") params.Day = timeutil.Format("Y-m-d")
} }
this.Data["clusterId"] = params.ClusterId
this.Data["nodeId"] = params.NodeId
this.Data["serverId"] = 0 this.Data["serverId"] = 0
this.Data["path"] = this.Request.URL.Path this.Data["path"] = this.Request.URL.Path
this.Data["day"] = params.Day this.Data["day"] = params.Day
@@ -66,6 +70,8 @@ func (this *IndexAction) RunGet(params struct {
var before = time.Now() var before = time.Now()
resp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{ resp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
RequestId: params.RequestId, RequestId: params.RequestId,
NodeClusterId: params.ClusterId,
NodeId: params.NodeId,
ServerId: params.ServerId, ServerId: params.ServerId,
HasError: params.HasError > 0, HasError: params.HasError > 0,
HasFirewallPolicy: params.HasWAF > 0, HasFirewallPolicy: params.HasWAF > 0,
@@ -108,6 +114,8 @@ func (this *IndexAction) RunGet(params struct {
this.Data["hasPrev"] = true this.Data["hasPrev"] = true
prevResp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{ prevResp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
RequestId: params.RequestId, RequestId: params.RequestId,
NodeClusterId: params.ClusterId,
NodeId: params.NodeId,
ServerId: params.ServerId, ServerId: params.ServerId,
HasError: params.HasError > 0, HasError: params.HasError > 0,
HasFirewallPolicy: params.HasWAF > 0, HasFirewallPolicy: params.HasWAF > 0,

View File

@@ -48,8 +48,9 @@ func (this *ViewPopupAction) RunGet(params struct {
if policyResp.HttpFirewallPolicy != nil { if policyResp.HttpFirewallPolicy != nil {
wafMap = maps.Map{ wafMap = maps.Map{
"policy": maps.Map{ "policy": maps.Map{
"id": policyResp.HttpFirewallPolicy.Id, "id": policyResp.HttpFirewallPolicy.Id,
"name": policyResp.HttpFirewallPolicy.Name, "name": policyResp.HttpFirewallPolicy.Name,
"serverId": policyResp.HttpFirewallPolicy.ServerId,
}, },
} }
if accessLog.FirewallRuleGroupId > 0 { if accessLog.FirewallRuleGroupId > 0 {

View File

@@ -64,6 +64,7 @@ func (this *IndexAction) RunGet(params struct {
"name": originConfig.Name, "name": originConfig.Name,
"isOn": originConfig.IsOn, "isOn": originConfig.IsOn,
"domains": originConfig.Domains, "domains": originConfig.Domains,
"hasCert": originConfig.Cert != nil,
} }
primaryOriginMaps = append(primaryOriginMaps, m) primaryOriginMaps = append(primaryOriginMaps, m)
} }
@@ -78,6 +79,7 @@ func (this *IndexAction) RunGet(params struct {
"name": originConfig.Name, "name": originConfig.Name,
"isOn": originConfig.IsOn, "isOn": originConfig.IsOn,
"domains": originConfig.Domains, "domains": originConfig.Domains,
"hasCert": originConfig.Cert != nil,
} }
backupOriginMaps = append(backupOriginMaps, m) backupOriginMaps = append(backupOriginMaps, m)
} }

View File

@@ -8,6 +8,7 @@ import (
"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/shared" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"net/url" "net/url"
"regexp" "regexp"
@@ -62,6 +63,8 @@ func (this *AddPopupAction) RunPost(params struct {
MaxIdleConns int32 MaxIdleConns int32
IdleTimeout int IdleTimeout int
CertIdsJSON []byte
DomainsJSON []byte DomainsJSON []byte
Description string Description string
@@ -129,6 +132,31 @@ func (this *AddPopupAction) RunPost(params struct {
return return
} }
// 证书
var certIds = []int64{}
if len(params.CertIdsJSON) > 0 {
err = json.Unmarshal(params.CertIdsJSON, &certIds)
if err != nil {
this.ErrorPage(err)
return
}
}
var certRefJSON []byte
if len(certIds) > 0 {
var certId = certIds[0]
if certId > 0 {
var certRef = &sslconfigs.SSLCertRef{
IsOn: true,
CertId: certId,
}
certRefJSON, err = json.Marshal(certRef)
if err != nil {
this.ErrorPage(err)
return
}
}
}
var domains = []string{} var domains = []string{}
if len(params.DomainsJSON) > 0 { if len(params.DomainsJSON) > 0 {
err = json.Unmarshal(params.DomainsJSON, &domains) err = json.Unmarshal(params.DomainsJSON, &domains)
@@ -158,6 +186,7 @@ func (this *AddPopupAction) RunPost(params struct {
IdleTimeoutJSON: idleTimeoutJSON, IdleTimeoutJSON: idleTimeoutJSON,
MaxConns: params.MaxConns, MaxConns: params.MaxConns,
MaxIdleConns: params.MaxIdleConns, MaxIdleConns: params.MaxIdleConns,
CertRefJSON: certRefJSON,
Domains: domains, Domains: domains,
}) })
if err != nil { if err != nil {

View File

@@ -7,6 +7,7 @@ import (
"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/shared" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
@@ -86,6 +87,12 @@ func (this *UpdatePopupAction) RunGet(params struct {
config.Domains = []string{} config.Domains = []string{}
} }
// 重置数据
if config.Cert != nil {
config.Cert.CertData = nil
config.Cert.KeyData = nil
}
this.Data["origin"] = maps.Map{ this.Data["origin"] = maps.Map{
"id": config.Id, "id": config.Id,
"protocol": config.Addr.Protocol, "protocol": config.Addr.Protocol,
@@ -99,6 +106,7 @@ func (this *UpdatePopupAction) RunGet(params struct {
"idleTimeout": idleTimeout, "idleTimeout": idleTimeout,
"maxConns": config.MaxConns, "maxConns": config.MaxConns,
"maxIdleConns": config.MaxIdleConns, "maxIdleConns": config.MaxIdleConns,
"cert": config.Cert,
"domains": config.Domains, "domains": config.Domains,
} }
@@ -121,6 +129,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
MaxIdleConns int32 MaxIdleConns int32
IdleTimeout int IdleTimeout int
CertIdsJSON []byte
DomainsJSON []byte DomainsJSON []byte
Description string Description string
@@ -188,6 +197,31 @@ func (this *UpdatePopupAction) RunPost(params struct {
return return
} }
// 证书
var certIds = []int64{}
if len(params.CertIdsJSON) > 0 {
err = json.Unmarshal(params.CertIdsJSON, &certIds)
if err != nil {
this.ErrorPage(err)
return
}
}
var certRefJSON []byte
if len(certIds) > 0 {
var certId = certIds[0]
if certId > 0 {
var certRef = &sslconfigs.SSLCertRef{
IsOn: true,
CertId: certId,
}
certRefJSON, err = json.Marshal(certRef)
if err != nil {
this.ErrorPage(err)
return
}
}
}
var domains = []string{} var domains = []string{}
if len(params.DomainsJSON) > 0 { if len(params.DomainsJSON) > 0 {
err = json.Unmarshal(params.DomainsJSON, &domains) err = json.Unmarshal(params.DomainsJSON, &domains)
@@ -218,6 +252,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
IdleTimeoutJSON: idleTimeoutJSON, IdleTimeoutJSON: idleTimeoutJSON,
MaxConns: params.MaxConns, MaxConns: params.MaxConns,
MaxIdleConns: params.MaxIdleConns, MaxIdleConns: params.MaxIdleConns,
CertRefJSON: certRefJSON,
Domains: domains, Domains: domains,
}) })
if err != nil { if err != nil {

View File

@@ -91,6 +91,7 @@ func (this *IndexAction) RunGet(params struct {
"name": originConfig.Name, "name": originConfig.Name,
"isOn": originConfig.IsOn, "isOn": originConfig.IsOn,
"domains": originConfig.Domains, "domains": originConfig.Domains,
"hasCert": originConfig.Cert != nil,
} }
primaryOriginMaps = append(primaryOriginMaps, m) primaryOriginMaps = append(primaryOriginMaps, m)
} }
@@ -105,6 +106,7 @@ func (this *IndexAction) RunGet(params struct {
"name": originConfig.Name, "name": originConfig.Name,
"isOn": originConfig.IsOn, "isOn": originConfig.IsOn,
"domains": originConfig.Domains, "domains": originConfig.Domains,
"hasCert": originConfig.Cert != nil,
} }
backupOriginMaps = append(backupOriginMaps, m) backupOriginMaps = append(backupOriginMaps, m)
} }

View File

@@ -85,6 +85,7 @@ func (this *GroupAction) RunGet(params struct {
"operator": rule.Operator, "operator": rule.Operator,
"value": rule.Value, "value": rule.Value,
"isCaseInsensitive": rule.IsCaseInsensitive, "isCaseInsensitive": rule.IsCaseInsensitive,
"description": rule.Description,
"isComposed": firewallconfigs.CheckCheckpointIsComposed(rule.Prefix()), "isComposed": firewallconfigs.CheckCheckpointIsComposed(rule.Prefix()),
"checkpointOptions": rule.CheckpointOptions, "checkpointOptions": rule.CheckpointOptions,
"err": errString, "err": errString,

View File

@@ -113,6 +113,7 @@ func (this *AllowListAction) RunGet(params struct {
"ipTo": item.IpTo, "ipTo": item.IpTo,
"createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt), "createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt),
"expiredTime": expiredTime, "expiredTime": expiredTime,
"lifeSeconds": item.ExpiredAt - time.Now().Unix(),
"reason": item.Reason, "reason": item.Reason,
"type": item.Type, "type": item.Type,
"isExpired": item.ExpiredAt > 0 && item.ExpiredAt < time.Now().Unix(), "isExpired": item.ExpiredAt > 0 && item.ExpiredAt < time.Now().Unix(),

View File

@@ -52,7 +52,7 @@ func (this *CountriesAction) RunGet(params struct {
return return
} }
countryMaps := []maps.Map{} countryMaps := []maps.Map{}
for _, country := range countriesResp.Countries { for _, country := range countriesResp.RegionCountries {
countryMaps = append(countryMaps, maps.Map{ countryMaps = append(countryMaps, maps.Map{
"id": country.Id, "id": country.Id,
"name": country.Name, "name": country.Name,

View File

@@ -113,6 +113,7 @@ func (this *DenyListAction) RunGet(params struct {
"ipTo": item.IpTo, "ipTo": item.IpTo,
"createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt), "createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt),
"expiredTime": expiredTime, "expiredTime": expiredTime,
"lifeSeconds": item.ExpiredAt - time.Now().Unix(),
"reason": item.Reason, "reason": item.Reason,
"type": item.Type, "type": item.Type,
"isExpired": item.ExpiredAt > 0 && item.ExpiredAt < time.Now().Unix(), "isExpired": item.ExpiredAt > 0 && item.ExpiredAt < time.Now().Unix(),

View File

@@ -47,14 +47,14 @@ func (this *ProvincesAction) RunGet(params struct {
} }
provincesResp, err := this.RPC().RegionProvinceRPC().FindAllEnabledRegionProvincesWithCountryId(this.AdminContext(), &pb.FindAllEnabledRegionProvincesWithCountryIdRequest{ provincesResp, err := this.RPC().RegionProvinceRPC().FindAllEnabledRegionProvincesWithCountryId(this.AdminContext(), &pb.FindAllEnabledRegionProvincesWithCountryIdRequest{
CountryId: int64(ChinaCountryId), RegionCountryId: int64(ChinaCountryId),
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
provinceMaps := []maps.Map{} provinceMaps := []maps.Map{}
for _, province := range provincesResp.Provinces { for _, province := range provincesResp.RegionProvinces {
provinceMaps = append(provinceMaps, maps.Map{ provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id, "id": province.Id,
"name": province.Name, "name": province.Name,

View File

@@ -378,6 +378,16 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
"isActive": secondMenuItem == "traffic", "isActive": secondMenuItem == "traffic",
"isOn": serverConfig.TrafficLimit != nil && serverConfig.TrafficLimit.IsOn, "isOn": serverConfig.TrafficLimit != nil && serverConfig.TrafficLimit.IsOn,
}) })
if serverConfig.Web != nil && serverConfig.Web.RequestScripts != nil {
_ = serverConfig.Web.RequestScripts.Init()
}
menuItems = append(menuItems, maps.Map{
"name": "边缘脚本",
"url": "/servers/server/settings/requestScripts?serverId=" + serverIdString,
"isActive": secondMenuItem == "requestScripts",
"isOn": serverConfig.Web != nil && serverConfig.Web.RequestScripts != nil && !serverConfig.Web.RequestScripts.IsEmpty(),
})
} }
menuItems = append(menuItems, maps.Map{ menuItems = append(menuItems, maps.Map{

View File

@@ -31,12 +31,12 @@ func (this *IndexAction) RunGet(params struct{}) {
// 国家和地区 // 国家和地区
countryMaps := []maps.Map{} countryMaps := []maps.Map{}
for _, countryId := range config.AllowCountryIds { for _, countryId := range config.AllowCountryIds {
countryResp, err := this.RPC().RegionCountryRPC().FindEnabledRegionCountry(this.AdminContext(), &pb.FindEnabledRegionCountryRequest{CountryId: countryId}) countryResp, err := this.RPC().RegionCountryRPC().FindEnabledRegionCountry(this.AdminContext(), &pb.FindEnabledRegionCountryRequest{RegionCountryId: countryId})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
country := countryResp.Country country := countryResp.RegionCountry
if country != nil { if country != nil {
countryMaps = append(countryMaps, maps.Map{ countryMaps = append(countryMaps, maps.Map{
"id": country.Id, "id": country.Id,
@@ -49,12 +49,12 @@ func (this *IndexAction) RunGet(params struct{}) {
// 省份 // 省份
provinceMaps := []maps.Map{} provinceMaps := []maps.Map{}
for _, provinceId := range config.AllowProvinceIds { for _, provinceId := range config.AllowProvinceIds {
provinceResp, err := this.RPC().RegionProvinceRPC().FindEnabledRegionProvince(this.AdminContext(), &pb.FindEnabledRegionProvinceRequest{ProvinceId: provinceId}) provinceResp, err := this.RPC().RegionProvinceRPC().FindEnabledRegionProvince(this.AdminContext(), &pb.FindEnabledRegionProvinceRequest{RegionProvinceId: provinceId})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
province := provinceResp.Province province := provinceResp.RegionProvince
if province != nil { if province != nil {
provinceMaps = append(provinceMaps, maps.Map{ provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id, "id": province.Id,

View File

@@ -29,7 +29,7 @@ func (this *SelectCountriesPopupAction) RunGet(params struct {
return return
} }
countryMaps := []maps.Map{} countryMaps := []maps.Map{}
for _, country := range countriesResp.Countries { for _, country := range countriesResp.RegionCountries {
countryMaps = append(countryMaps, maps.Map{ countryMaps = append(countryMaps, maps.Map{
"id": country.Id, "id": country.Id,
"name": country.Name, "name": country.Name,
@@ -50,12 +50,12 @@ func (this *SelectCountriesPopupAction) RunPost(params struct {
}) { }) {
countryMaps := []maps.Map{} countryMaps := []maps.Map{}
for _, countryId := range params.CountryIds { for _, countryId := range params.CountryIds {
countryResp, err := this.RPC().RegionCountryRPC().FindEnabledRegionCountry(this.AdminContext(), &pb.FindEnabledRegionCountryRequest{CountryId: countryId}) countryResp, err := this.RPC().RegionCountryRPC().FindEnabledRegionCountry(this.AdminContext(), &pb.FindEnabledRegionCountryRequest{RegionCountryId: countryId})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
country := countryResp.Country country := countryResp.RegionCountry
if country == nil { if country == nil {
continue continue
} }

View File

@@ -24,13 +24,13 @@ func (this *SelectProvincesPopupAction) RunGet(params struct {
}) { }) {
selectedProvinceIds := utils.SplitNumbers(params.ProvinceIds) selectedProvinceIds := utils.SplitNumbers(params.ProvinceIds)
provincesResp, err := this.RPC().RegionProvinceRPC().FindAllEnabledRegionProvincesWithCountryId(this.AdminContext(), &pb.FindAllEnabledRegionProvincesWithCountryIdRequest{CountryId: ChinaCountryId}) provincesResp, err := this.RPC().RegionProvinceRPC().FindAllEnabledRegionProvincesWithCountryId(this.AdminContext(), &pb.FindAllEnabledRegionProvincesWithCountryIdRequest{RegionCountryId: ChinaCountryId})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
provinceMaps := []maps.Map{} provinceMaps := []maps.Map{}
for _, province := range provincesResp.Provinces { for _, province := range provincesResp.RegionProvinces {
provinceMaps = append(provinceMaps, maps.Map{ provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id, "id": province.Id,
"name": province.Name, "name": province.Name,
@@ -50,12 +50,12 @@ func (this *SelectProvincesPopupAction) RunPost(params struct {
}) { }) {
provinceMaps := []maps.Map{} provinceMaps := []maps.Map{}
for _, provinceId := range params.ProvinceIds { for _, provinceId := range params.ProvinceIds {
provinceResp, err := this.RPC().RegionProvinceRPC().FindEnabledRegionProvince(this.AdminContext(), &pb.FindEnabledRegionProvinceRequest{ProvinceId: provinceId}) provinceResp, err := this.RPC().RegionProvinceRPC().FindEnabledRegionProvince(this.AdminContext(), &pb.FindEnabledRegionProvinceRequest{RegionProvinceId: provinceId})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
province := provinceResp.Province province := provinceResp.RegionProvince
if province == nil { if province == nil {
continue continue
} }

View File

@@ -16,9 +16,16 @@ func (this *IndexAction) Init() {
} }
func (this *IndexAction) RunGet(params struct { func (this *IndexAction) RunGet(params struct {
Keyword string Keyword string
Verifying bool
}) { }) {
countResp, err := this.RPC().UserRPC().CountAllEnabledUsers(this.AdminContext(), &pb.CountAllEnabledUsersRequest{Keyword: params.Keyword}) this.Data["keyword"] = params.Keyword
this.Data["isVerifying"] = params.Verifying
countResp, err := this.RPC().UserRPC().CountAllEnabledUsers(this.AdminContext(), &pb.CountAllEnabledUsersRequest{
Keyword: params.Keyword,
IsVerifying: params.Verifying,
})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
@@ -28,15 +35,16 @@ func (this *IndexAction) RunGet(params struct {
this.Data["page"] = page.AsHTML() this.Data["page"] = page.AsHTML()
usersResp, err := this.RPC().UserRPC().ListEnabledUsers(this.AdminContext(), &pb.ListEnabledUsersRequest{ usersResp, err := this.RPC().UserRPC().ListEnabledUsers(this.AdminContext(), &pb.ListEnabledUsersRequest{
Keyword: params.Keyword, Keyword: params.Keyword,
Offset: page.Offset, IsVerifying: params.Verifying,
Size: page.Size, Offset: page.Offset,
Size: page.Size,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
userMaps := []maps.Map{} var userMaps = []maps.Map{}
for _, user := range usersResp.Users { for _, user := range usersResp.Users {
var clusterMap maps.Map = nil var clusterMap maps.Map = nil
if user.NodeCluster != nil { if user.NodeCluster != nil {
@@ -45,16 +53,20 @@ func (this *IndexAction) RunGet(params struct {
"name": user.NodeCluster.Name, "name": user.NodeCluster.Name,
} }
} }
userMaps = append(userMaps, maps.Map{ userMaps = append(userMaps, maps.Map{
"id": user.Id, "id": user.Id,
"username": user.Username, "username": user.Username,
"isOn": user.IsOn, "isOn": user.IsOn,
"fullname": user.Fullname, "fullname": user.Fullname,
"email": user.Email, "email": user.Email,
"mobile": user.Mobile, "mobile": user.Mobile,
"tel": user.Tel, "tel": user.Tel,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", user.CreatedAt), "createdTime": timeutil.FormatTime("Y-m-d H:i:s", user.CreatedAt),
"cluster": clusterMap, "cluster": clusterMap,
"registeredIP": user.RegisteredIP,
"isVerified": user.IsVerified,
"isRejected": user.IsRejected,
}) })
} }
this.Data["users"] = userMaps this.Data["users"] = userMaps

View File

@@ -19,6 +19,7 @@ func init() {
GetPost("/update", new(UpdateAction)). GetPost("/update", new(UpdateAction)).
Post("/delete", new(DeleteAction)). Post("/delete", new(DeleteAction)).
GetPost("/features", new(FeaturesAction)). GetPost("/features", new(FeaturesAction)).
GetPost("/verifyPopup", new(VerifyPopupAction)).
// AccessKeys // AccessKeys
Prefix("/users/accessKeys"). Prefix("/users/accessKeys").
@@ -26,7 +27,6 @@ func init() {
GetPost("/createPopup", new(accesskeys.CreatePopupAction)). GetPost("/createPopup", new(accesskeys.CreatePopupAction)).
Post("/delete", new(accesskeys.DeleteAction)). Post("/delete", new(accesskeys.DeleteAction)).
Post("/updateIsOn", new(accesskeys.UpdateIsOnAction)). Post("/updateIsOn", new(accesskeys.UpdateIsOnAction)).
EndAll() EndAll()
}) })
} }

View File

@@ -20,6 +20,11 @@ func (this *UserAction) RunGet(params struct {
}) { }) {
err := userutils.InitUser(this.Parent(), params.UserId) err := userutils.InitUser(this.Parent(), params.UserId)
if err != nil { if err != nil {
if err == userutils.ErrUserNotFound {
this.RedirectURL("/users")
return
}
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
@@ -51,17 +56,35 @@ func (this *UserAction) RunGet(params struct {
} }
countAccessKeys := countAccessKeyResp.Count countAccessKeys := countAccessKeyResp.Count
// IP地址
var registeredRegion = ""
if len(user.RegisteredIP) > 0 {
regionResp, err := this.RPC().IPLibraryRPC().LookupIPRegion(this.AdminContext(), &pb.LookupIPRegionRequest{Ip: user.RegisteredIP})
if err != nil {
this.ErrorPage(err)
return
}
if regionResp.IpRegion != nil {
registeredRegion = regionResp.IpRegion.Summary
}
}
this.Data["user"] = maps.Map{ this.Data["user"] = maps.Map{
"id": user.Id, "id": user.Id,
"username": user.Username, "username": user.Username,
"fullname": user.Fullname, "fullname": user.Fullname,
"email": user.Email, "email": user.Email,
"tel": user.Tel, "tel": user.Tel,
"remark": user.Remark, "remark": user.Remark,
"mobile": user.Mobile, "mobile": user.Mobile,
"isOn": user.IsOn, "isOn": user.IsOn,
"cluster": clusterMap, "cluster": clusterMap,
"countAccessKeys": countAccessKeys, "countAccessKeys": countAccessKeys,
"isRejected": user.IsRejected,
"rejectReason": user.RejectReason,
"isVerified": user.IsVerified,
"registeredIP": user.RegisteredIP,
"registeredRegion": registeredRegion,
} }
this.Show() this.Show()

View File

@@ -7,6 +7,8 @@ import (
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
) )
var ErrUserNotFound = errors.New("not found user")
// InitUser 查找用户基本信息 // InitUser 查找用户基本信息
func InitUser(p *actionutils.ParentAction, userId int64) error { func InitUser(p *actionutils.ParentAction, userId int64) error {
resp, err := p.RPC().UserRPC().FindEnabledUser(p.AdminContext(), &pb.FindEnabledUserRequest{UserId: userId}) resp, err := p.RPC().UserRPC().FindEnabledUser(p.AdminContext(), &pb.FindEnabledUserRequest{UserId: userId})
@@ -14,7 +16,7 @@ func InitUser(p *actionutils.ParentAction, userId int64) error {
return err return err
} }
if resp.User == nil { if resp.User == nil {
return errors.New("not found user") return ErrUserNotFound
} }
// AccessKey数量 // AccessKey数量

View File

@@ -0,0 +1,56 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package users
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type VerifyPopupAction struct {
actionutils.ParentAction
}
func (this *VerifyPopupAction) RunGet(params struct {
UserId int64
}) {
this.Data["userId"] = params.UserId
this.Show()
}
func (this *VerifyPopupAction) RunPost(params struct {
UserId int64
Result string
RejectReason string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("审核用户:%d 结果:%s", params.UserId, params.Result)
if params.Result == "pass" {
params.RejectReason = ""
}
_, err := this.RPC().UserRPC().VerifyUser(this.AdminContext(), &pb.VerifyUserRequest{
UserId: params.UserId,
IsRejected: params.Result == "reject" || params.Result == "delete",
RejectReason: params.RejectReason,
})
if err != nil {
this.ErrorPage(err)
return
}
if params.Result == "delete" {
_, err = this.RPC().UserRPC().DeleteUser(this.AdminContext(), &pb.DeleteUserRequest{UserId: params.UserId})
if err != nil {
this.ErrorPage(err)
return
}
}
this.Success()
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
) )
func FindAllMenuMaps(nodeLogsType string, countUnreadNodeLogs int64) []maps.Map { func FindAllMenuMaps(nodeLogsType string, countUnreadNodeLogs int64, countUnreadIPItems int64) []maps.Map {
return []maps.Map{ return []maps.Map{
{ {
"code": "dashboard", "code": "dashboard",
@@ -50,9 +50,10 @@ func FindAllMenuMaps(nodeLogsType string, countUnreadNodeLogs int64) []maps.Map
"code": "waf", "code": "waf",
}, },
{ {
"name": "IP名单", "name": "IP名单",
"url": "/servers/iplists", "url": "/servers/iplists",
"code": "iplist", "code": "iplist",
"badge": countUnreadIPItems,
}, },
{ {
"name": "统计指标", "name": "统计指标",

View File

@@ -171,16 +171,21 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
// 菜单配置 // 菜单配置
func (this *userMustAuth) modules(actionPtr actions.ActionWrapper, adminId int64) []maps.Map { func (this *userMustAuth) modules(actionPtr actions.ActionWrapper, adminId int64) []maps.Map {
// 运行日志
var countUnreadNodeLogs int64 = 0 var countUnreadNodeLogs int64 = 0
var nodeLogsType = "" var nodeLogsType = ""
// IP名单
var countUnreadIPItems int64 = 0
// 父级动作 // 父级动作
parentAction, ok := actionPtr.(actionutils.ActionInterface) parentAction, ok := actionPtr.(actionutils.ActionInterface)
if ok { if ok {
var action = actionPtr.Object() var action = actionPtr.Object()
// 未读日志数 // 未读日志数
if action.Data.GetString("teaMenu") == "clusters" { var mainMenu = action.Data.GetString("teaMenu")
if mainMenu == "clusters" {
countNodeLogsResp, err := parentAction.RPC().NodeLogRPC().CountNodeLogs(parentAction.AdminContext(), &pb.CountNodeLogsRequest{ countNodeLogsResp, err := parentAction.RPC().NodeLogRPC().CountNodeLogs(parentAction.AdminContext(), &pb.CountNodeLogsRequest{
Role: nodeconfigs.NodeRoleNode, Role: nodeconfigs.NodeRoleNode,
IsUnread: true, IsUnread: true,
@@ -197,11 +202,18 @@ func (this *userMustAuth) modules(actionPtr actions.ActionWrapper, adminId int64
nodeLogsType = "unread" nodeLogsType = "unread"
} }
} }
} else if mainMenu == "servers" {
countUnreadIPItemsResp, err := parentAction.RPC().IPItemRPC().CountAllEnabledIPItems(parentAction.AdminContext(), &pb.CountAllEnabledIPItemsRequest{Unread: true})
if err != nil {
logs.Error(err)
} else {
countUnreadIPItems = countUnreadIPItemsResp.Count
}
} }
} }
result := []maps.Map{} result := []maps.Map{}
for _, m := range FindAllMenuMaps(nodeLogsType, countUnreadNodeLogs) { for _, m := range FindAllMenuMaps(nodeLogsType, countUnreadNodeLogs, countUnreadIPItems) {
if m.GetString("code") == "finance" && !configloaders.ShowFinance() { if m.GetString("code") == "finance" && !configloaders.ShowFinance() {
continue continue
} }

View File

@@ -321,6 +321,33 @@ Vue.component("cluster-selector", {
</div>` </div>`
}) })
Vue.component("node-cluster-combo-box", {
props: ["v-cluster-id"],
data: function () {
let that = this
Tea.action("/clusters/options")
.post()
.success(function (resp) {
that.clusters = resp.data.clusters
})
return {
clusters: []
}
},
methods: {
change: function (item) {
if (item == null) {
this.$emit("change", 0)
} else {
this.$emit("change", item.value)
}
}
},
template: `<div v-if="clusters.length > 0">
<combo-box title="集群" placeholder="集群名称" :v-items="clusters" name="clusterId" :v-value="vClusterId" @change="change"></combo-box>
</div>`
})
// 一个节点的多个集群选择器 // 一个节点的多个集群选择器
Vue.component("node-clusters-selector", { Vue.component("node-clusters-selector", {
props: ["v-primary-cluster", "v-secondary-clusters"], props: ["v-primary-cluster", "v-secondary-clusters"],
@@ -2203,6 +2230,44 @@ Vue.component("http-firewall-actions-view", {
</div>` </div>`
}) })
Vue.component("http-request-scripts-config-box", {
props: ["vRequestScriptsConfig"],
data: function () {
let config = this.vRequestScriptsConfig
if (config == null) {
config = {}
}
return {
config: config
}
},
methods: {
changeInitGroup: function (group) {
this.config.initGroup = group
this.$forceUpdate()
},
changeRequestGroup: function (group) {
this.config.requestGroup = group
this.$forceUpdate()
}
},
template: `<div>
<input type="hidden" name="requestScriptsJSON" :value="JSON.stringify(config)"/>
<div class="margin"></div>
<h4 style="margin-bottom: 0">请求初始化</h4>
<p class="comment">在请求刚初始化时调用此时自定义Header等尚未生效。</p>
<div>
<script-group-config-box :v-group="config.initGroup" @change="changeInitGroup"></script-group-config-box>
</div>
<h4 style="margin-bottom: 0">准备发送请求</h4>
<p class="comment">在准备执行请求或者转发请求之前调用此时自定义Header、源站等已准备好。</p>
<div>
<script-group-config-box :v-group="config.requestGroup" @change="changeRequestGroup"></script-group-config-box>
</div>
<div class="margin"></div>
</div>`
})
// 显示WAF规则的标签 // 显示WAF规则的标签
Vue.component("http-firewall-rule-label", { Vue.component("http-firewall-rule-label", {
props: ["v-rule"], props: ["v-rule"],
@@ -2219,7 +2284,7 @@ Vue.component("http-firewall-rule-label", {
}, },
template: `<div> template: `<div>
<div class="ui label tiny basic"> <div class="ui label tiny basic" style="line-height: 1.5">
{{rule.name}}[{{rule.param}}] {{rule.name}}[{{rule.param}}]
<!-- cc2 --> <!-- cc2 -->
@@ -2238,6 +2303,9 @@ Vue.component("http-firewall-rule-label", {
{{rule.value}} {{rule.value}}
</span> </span>
<!-- description -->
<span v-if="rule.description != null && rule.description.length > 0" class="grey small">{{rule.description}}</span>
<a href="" v-if="rule.err != null && rule.err.length > 0" @click.prevent="showErr(rule.err)" style="color: #db2828; opacity: 1; border-bottom: 1px #db2828 dashed; margin-left: 0.5em">规则错误</a> <a href="" v-if="rule.err != null && rule.err.length > 0" @click.prevent="showErr(rule.err)" style="color: #db2828; opacity: 1; border-bottom: 1px #db2828 dashed; margin-left: 0.5em">规则错误</a>
</div> </div>
</div>` </div>`
@@ -2317,18 +2385,29 @@ Vue.component("http-cache-refs-box", {
Vue.component("ssl-certs-box", { Vue.component("ssl-certs-box", {
props: [ props: [
"v-certs", // 证书列表 "v-certs", // 证书列表
"v-cert", // 单个证书
"v-protocol", // 协议https|tls "v-protocol", // 协议https|tls
"v-view-size", // 弹窗尺寸 "v-view-size", // 弹窗尺寸normal, mini
"v-single-mode" // 单证书模式 "v-single-mode", // 单证书模式
"v-description" // 描述文字
], ],
data: function () { data: function () {
let certs = this.vCerts let certs = this.vCerts
if (certs == null) { if (certs == null) {
certs = [] certs = []
} }
if (this.vCert != null) {
certs.push(this.vCert)
}
let description = this.vDescription
if (description == null || typeof (description) != "string") {
description = ""
}
return { return {
certs: certs certs: certs,
description: description
} }
}, },
methods: { methods: {
@@ -2393,13 +2472,14 @@ Vue.component("ssl-certs-box", {
template: `<div> template: `<div>
<input type="hidden" name="certIdsJSON" :value="JSON.stringify(certIds())"/> <input type="hidden" name="certIdsJSON" :value="JSON.stringify(certIds())"/>
<div v-if="certs != null && certs.length > 0"> <div v-if="certs != null && certs.length > 0">
<div class="ui label small" v-for="(cert, index) in certs"> <div class="ui label small basic" v-for="(cert, index) in certs">
{{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}} &nbsp; <a href="" title="删除" @click.prevent="removeCert(index)"><i class="icon remove"></i></a> {{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}} &nbsp; <a href="" title="删除" @click.prevent="removeCert(index)"><i class="icon remove"></i></a>
</div> </div>
<div class="ui divider" v-if="buttonsVisible()"></div> <div class="ui divider" v-if="buttonsVisible()"></div>
</div> </div>
<div v-else> <div v-else>
<span class="red">选择或上传证书后<span v-if="vProtocol == 'https'">HTTPS</span><span v-if="vProtocol == 'tls'">TLS</span>服务才能生效。</span> <span class="red" v-if="description.length == 0">选择或上传证书后<span v-if="vProtocol == 'https'">HTTPS</span><span v-if="vProtocol == 'tls'">TLS</span>服务才能生效。</span>
<span class="grey" v-if="description.length > 0">{{description}}</span>
<div class="ui divider" v-if="buttonsVisible()"></div> <div class="ui divider" v-if="buttonsVisible()"></div>
</div> </div>
<div v-if="buttonsVisible()"> <div v-if="buttonsVisible()">
@@ -4091,7 +4171,7 @@ Vue.component("http-cache-refs-config-box", {
</table> </table>
<p class="comment" v-if="refs.length > 1">所有条件匹配顺序为从上到下,可以拖动左侧的<i class="icon bars"></i>排序。服务设置的优先级比全局缓存策略设置的优先级要高。</p> <p class="comment" v-if="refs.length > 1">所有条件匹配顺序为从上到下,可以拖动左侧的<i class="icon bars"></i>排序。服务设置的优先级比全局缓存策略设置的优先级要高。</p>
<button class="ui button tiny" @click.prevent="addRef(false)">+添加缓存设置</button> &nbsp; &nbsp; <a href="" @click.prevent="addRef(true)">+添加不缓存设置</a> <button class="ui button tiny" @click.prevent="addRef(false)" type="button">+添加缓存设置</button> &nbsp; &nbsp; <a href="" @click.prevent="addRef(true)">+添加不缓存设置</a>
</div> </div>
<div class="margin"></div> <div class="margin"></div>
</div>` </div>`
@@ -4191,6 +4271,9 @@ Vue.component("origin-list-table", {
<div v-if="origin.domains != null && origin.domains.length > 0"> <div v-if="origin.domains != null && origin.domains.length > 0">
<grey-label v-for="domain in origin.domains">{{domain}}</grey-label> <grey-label v-for="domain in origin.domains">{{domain}}</grey-label>
</div> </div>
<div v-if="origin.hasCert">
<tiny-basic-label>证书</tiny-basic-label>
</div>
</td> </td>
<td :class="{disabled:!origin.isOn}">{{origin.weight}}</td> <td :class="{disabled:!origin.isOn}">{{origin.weight}}</td>
<td> <td>
@@ -5009,6 +5092,8 @@ Vue.component("http-firewall-actions-box", {
blockScope: "global", blockScope: "global",
captchaLife: "", captchaLife: "",
captchaMaxFails: "",
captchaFailBlockTimeout: "",
get302Life: "", get302Life: "",
post307Life: "", post307Life: "",
recordIPType: "black", recordIPType: "black",
@@ -5057,6 +5142,22 @@ Vue.component("http-firewall-actions-box", {
this.actionOptions["life"] = v this.actionOptions["life"] = v
} }
}, },
captchaMaxFails: function (v) {
v = parseInt(v)
if (isNaN(v)) {
this.actionOptions["maxFails"] = 0
} else {
this.actionOptions["maxFails"] = v
}
},
captchaFailBlockTimeout: function (v) {
v = parseInt(v)
if (isNaN(v)) {
this.actionOptions["failBlockTimeout"] = 0
} else {
this.actionOptions["failBlockTimeout"] = v
}
},
get302Life: function (v) { get302Life: function (v) {
v = parseInt(v) v = parseInt(v)
if (isNaN(v)) { if (isNaN(v)) {
@@ -5123,6 +5224,8 @@ Vue.component("http-firewall-actions-box", {
this.blockTimeout = "" this.blockTimeout = ""
this.blockScope = "global" this.blockScope = "global"
this.captchaLife = "" this.captchaLife = ""
this.captchaMaxFails = ""
this.captchaFailBlockTimeout = ""
this.get302Life = "" this.get302Life = ""
this.post307Life = "" this.post307Life = ""
@@ -5191,6 +5294,14 @@ Vue.component("http-firewall-actions-box", {
if (config.options.life != null || config.options.life > 0) { if (config.options.life != null || config.options.life > 0) {
this.captchaLife = config.options.life.toString() this.captchaLife = config.options.life.toString()
} }
this.captchaMaxFails = ""
if (config.options.maxFails != null || config.options.maxFails > 0) {
this.captchaMaxFails = config.options.maxFails.toString()
}
this.captchaFailBlockTimeout = ""
if (config.options.failBlockTimeout != null || config.options.failBlockTimeout > 0) {
this.captchaFailBlockTimeout = config.options.failBlockTimeout.toString()
}
break break
case "notify": case "notify":
break break
@@ -5492,7 +5603,7 @@ Vue.component("http-firewall-actions-box", {
<td>封锁时间</td> <td>封锁时间</td>
<td> <td>
<div class="ui input right labeled"> <div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="10" v-model="blockTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/> <input type="text" style="width: 5em" maxlength="9" v-model="blockTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span> <span class="ui label">秒</span>
</div> </div>
</td> </td>
@@ -5512,19 +5623,39 @@ Vue.component("http-firewall-actions-box", {
<td>有效时间</td> <td>有效时间</td>
<td> <td>
<div class="ui input right labeled"> <div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="10" v-model="captchaLife" @keyup.enter="confirm()" @keypress.enter.prevent="1"/> <input type="text" style="width: 5em" maxlength="9" v-model="captchaLife" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span> <span class="ui label">秒</span>
</div> </div>
<p class="comment">验证通过后在这个时间内不再验证默认600秒。</p> <p class="comment">验证通过后在这个时间内不再验证默认600秒。</p>
</td> </td>
</tr> </tr>
<tr v-if="actionCode == 'captcha'">
<td>最多失败次数</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="9" v-model="captchaMaxFails" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">次</span>
</div>
<p class="comment">如果为空或者为0表示不限制。</p>
</td>
</tr>
<tr v-if="actionCode == 'captcha'">
<td>失败拦截时间</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="9" v-model="captchaFailBlockTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span>
</div>
<p class="comment">在达到最多失败次数大于0自动拦截的时间如果为0表示不自动拦截。</p>
</td>
</tr>
<!-- get_302 --> <!-- get_302 -->
<tr v-if="actionCode == 'get_302'"> <tr v-if="actionCode == 'get_302'">
<td>有效时间</td> <td>有效时间</td>
<td> <td>
<div class="ui input right labeled"> <div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="10" v-model="get302Life" @keyup.enter="confirm()" @keypress.enter.prevent="1"/> <input type="text" style="width: 5em" maxlength="9" v-model="get302Life" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span> <span class="ui label">秒</span>
</div> </div>
<p class="comment">验证通过后在这个时间内不再验证。</p> <p class="comment">验证通过后在这个时间内不再验证。</p>
@@ -5536,7 +5667,7 @@ Vue.component("http-firewall-actions-box", {
<td>有效时间</td> <td>有效时间</td>
<td> <td>
<div class="ui input right labeled"> <div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="10" v-model="post307Life" @keyup.enter="confirm()" @keypress.enter.prevent="1"/> <input type="text" style="width: 5em" maxlength="9" v-model="post307Life" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span> <span class="ui label">秒</span>
</div> </div>
<p class="comment">验证通过后在这个时间内不再验证。</p> <p class="comment">验证通过后在这个时间内不再验证。</p>
@@ -5573,7 +5704,7 @@ Vue.component("http-firewall-actions-box", {
<td>超时时间</td> <td>超时时间</td>
<td> <td>
<div class="ui input right labeled"> <div class="ui input right labeled">
<input type="text" style="width: 6em" maxlength="10" v-model="recordIPTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/> <input type="text" style="width: 6em" maxlength="9" v-model="recordIPTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span> <span class="ui label">秒</span>
</div> </div>
<p class="comment">0表示不超时。</p> <p class="comment">0表示不超时。</p>
@@ -5984,7 +6115,7 @@ Vue.component("http-header-policy-box", {
<submit-btn></submit-btn> <submit-btn></submit-btn>
</div> </div>
<div v-if="((!vIsLocation && !vIsGroup) || requestHeaderRef.isPrior) && type == 'response'"> <div v-if="((!vIsLocation && !vIsGroup) || responseHeaderRef.isPrior) && type == 'response'">
<div v-if="vHasGroupResponseConfig"> <div v-if="vHasGroupResponseConfig">
<div class="margin"></div> <div class="margin"></div>
<warning-message>由于已经在当前<a :href="vGroupSettingUrl + '#response'">服务分组</a>中进行了对应的配置,在这里的配置将不会生效。</warning-message> <warning-message>由于已经在当前<a :href="vGroupSettingUrl + '#response'">服务分组</a>中进行了对应的配置,在这里的配置将不会生效。</warning-message>
@@ -6201,7 +6332,7 @@ Vue.component("http-pages-and-shutdown-box", {
<h1>网站升级中</h1> <h1>网站升级中</h1>
<p>为了给您提供更好的服务,我们正在升级网站,请稍后重新访问。</p> <p>为了给您提供更好的服务,我们正在升级网站,请稍后重新访问。</p>
<address>Request ID: \${requestId}, Powered by GoEdge.</address> <address>Request ID: \${requestId}.</address>
</body> </body>
</html>` </html>`
@@ -7220,6 +7351,60 @@ Vue.component("http-gzip-box", {
</div>` </div>`
}) })
Vue.component("script-config-box", {
props: ["id", "v-script-config", "comment"],
data: function () {
let config = this.vScriptConfig
if (config == null) {
config = {
isPrior: false,
isOn: false,
code: ""
}
}
if (config.code.length == 0) {
config.code = "\n\n\n\n"
}
return {
config: config
}
},
watch: {
"config.isOn": function () {
this.change()
}
},
methods: {
change: function () {
this.$emit("change", this.config)
},
changeCode: function (code) {
this.config.code = code
this.change()
}
},
template: `<div>
<table class="ui table definition selectable">
<tbody>
<tr>
<td class="title">是否启用</td>
<td><checkbox v-model="config.isOn"></checkbox></td>
</tr>
</tbody>
<tbody>
<tr :style="{opacity: !config.isOn ? 0.5 : 1}">
<td>脚本代码</td>
<td><source-code-box :id="id" type="text/javascript" :read-only="false" @change="changeCode">{{config.code}}</source-code-box>
<p class="comment">{{comment}}</p>
</td>
</tr>
</tbody>
</table>
</div>`
})
Vue.component("ssl-certs-view", { Vue.component("ssl-certs-view", {
props: ["v-certs"], props: ["v-certs"],
data: function () { data: function () {
@@ -7741,7 +7926,7 @@ Vue.component("http-remote-addr-config-box", {
// 访问日志搜索框 // 访问日志搜索框
Vue.component("http-access-log-search-box", { Vue.component("http-access-log-search-box", {
props: ["v-ip", "v-domain", "v-keyword"], props: ["v-ip", "v-domain", "v-keyword", "v-cluster-id", "v-node-id"],
data: function () { data: function () {
let ip = this.vIp let ip = this.vIp
if (ip == null) { if (ip == null) {
@@ -7761,7 +7946,8 @@ Vue.component("http-access-log-search-box", {
return { return {
ip: ip, ip: ip,
domain: domain, domain: domain,
keyword: keyword keyword: keyword,
clusterId: this.vClusterId
} }
}, },
methods: { methods: {
@@ -7793,9 +7979,12 @@ Vue.component("http-access-log-search-box", {
parent.submit() parent.submit()
}, 500) }, 500)
} }
},
changeCluster: function (clusterId) {
this.clusterId = clusterId
} }
}, },
template: `<div> template: `<div style="z-index: 10">
<div class="margin"></div> <div class="margin"></div>
<div class="ui fields inline"> <div class="ui fields inline">
<div class="ui field"> <div class="ui field">
@@ -7820,8 +8009,16 @@ Vue.component("http-access-log-search-box", {
</div> </div>
</div> </div>
<slot></slot> <slot></slot>
</div>
<div class="ui fields inline" style="margin-top: 0.5em">
<div class="ui field"> <div class="ui field">
<button class="ui button small" type="submit">查找</button> <node-cluster-combo-box :v-cluster-id="clusterId" @change="changeCluster"></node-cluster-combo-box>
</div>
<div class="ui field" v-if="clusterId > 0">
<node-combo-box :v-cluster-id="clusterId" :v-node-id="vNodeId"></node-combo-box>
</div>
<div class="ui field">
<button class="ui button small" type="submit">搜索日志</button>
</div> </div>
</div> </div>
</div>` </div>`
@@ -8320,7 +8517,8 @@ Vue.component("http-firewall-block-options", {
return { return {
blockOptions: this.vBlockOptions, blockOptions: this.vBlockOptions,
statusCode: this.vBlockOptions.statusCode, statusCode: this.vBlockOptions.statusCode,
timeout: this.vBlockOptions.timeout timeout: this.vBlockOptions.timeout,
isEditing: false
} }
}, },
watch: { watch: {
@@ -8341,9 +8539,15 @@ Vue.component("http-firewall-block-options", {
} }
} }
}, },
methods: {
edit: function () {
this.isEditing = !this.isEditing
}
},
template: `<div> template: `<div>
<input type="hidden" name="blockOptionsJSON" :value="JSON.stringify(blockOptions)"/> <input type="hidden" name="blockOptionsJSON" :value="JSON.stringify(blockOptions)"/>
<table class="ui table"> <a href="" @click.prevent="edit">状态码:{{statusCode}} / 提示内容:<span v-if="blockOptions.body != null && blockOptions.body.length > 0">[{{blockOptions.body.length}}字符]</span><span v-else class="disabled">[无]</span> / 超时时间:{{timeout}}秒 <i class="icon angle" :class="{up: isEditing, down: !isEditing}"></i></a>
<table class="ui table" v-show="isEditing">
<tr> <tr>
<td class="title">状态码</td> <td class="title">状态码</td>
<td> <td>
@@ -8428,6 +8632,9 @@ Vue.component("http-firewall-rules-box", {
<span v-if="rule.paramFilters != null && rule.paramFilters.length > 0" v-for="paramFilter in rule.paramFilters"> | {{paramFilter.code}}</span> <var>{{rule.operator}}</var> {{rule.value}} <span v-if="rule.paramFilters != null && rule.paramFilters.length > 0" v-for="paramFilter in rule.paramFilters"> | {{paramFilter.code}}</span> <var>{{rule.operator}}</var> {{rule.value}}
</span> </span>
<!-- description -->
<span v-if="rule.description != null && rule.description.length > 0" class="grey small">{{rule.description}}</span>
<a href="" title="修改" @click.prevent="updateRule(index, rule)"><i class="icon pencil small"></i></a> <a href="" title="修改" @click.prevent="updateRule(index, rule)"><i class="icon pencil small"></i></a>
<a href="" title="删除" @click.prevent="removeRule(index)"><i class="icon remove"></i></a> <a href="" title="删除" @click.prevent="removeRule(index)"><i class="icon remove"></i></a>
</div> </div>
@@ -9540,6 +9747,45 @@ Vue.component("server-group-selector", {
</div>` </div>`
}) })
Vue.component("script-group-config-box", {
props: ["v-group"],
data: function () {
let group = this.vGroup
if (group == null) {
group = {
isPrior: false,
isOn: true,
scripts: []
}
}
if (group.scripts == null) {
group.scripts = []
}
let script = null
if (group.scripts.length > 0) {
script = group.scripts[group.scripts.length - 1]
}
return {
group: group,
script: script
}
},
methods: {
changeScript: function (script) {
this.group.scripts = [script] // 目前只支持单个脚本
this.change()
},
change: function () {
this.$emit("change", this.group)
}
},
template: `<div>
<script-config-box :v-script-config="script" comment="在接收到客户端请求之后立即调用。预置req、resp变量。" @change="changeScript"></script-config-box>
</div>`
})
// 指标周期设置 // 指标周期设置
Vue.component("metric-period-config-box", { Vue.component("metric-period-config-box", {
props: ["v-period", "v-period-unit"], props: ["v-period", "v-period-unit"],
@@ -9693,6 +9939,99 @@ Vue.component("traffic-limit-config-box", {
</div>` </div>`
}) })
Vue.component("firewall-syn-flood-config-box", {
props: ["v-syn-flood-config"],
data: function () {
let config = this.vSynFloodConfig
if (config == null) {
config = {
isOn: false,
minAttempts: 10,
timeoutSeconds: 600,
ignoreLocal: true
}
}
return {
config: config,
isEditing: false,
minAttempts: config.minAttempts,
timeoutSeconds: config.timeoutSeconds
}
},
methods: {
edit: function () {
this.isEditing = !this.isEditing
}
},
watch: {
minAttempts: function (v) {
let count = parseInt(v)
if (isNaN(count)) {
count = 10
}
if (count < 5) {
count = 5
}
this.config.minAttempts = count
},
timeoutSeconds: function (v) {
let seconds = parseInt(v)
if (isNaN(seconds)) {
seconds = 10
}
if (seconds < 60) {
seconds = 60
}
this.config.timeoutSeconds = seconds
}
},
template: `<div>
<input type="hidden" name="synFloodJSON" :value="JSON.stringify(config)"/>
<a href="" @click.prevent="edit">
<span v-if="config.isOn">
已启用 / <span>空连接次数:{{config.minAttempts}}次/分钟</span> / 封禁时间:{{config.timeoutSeconds}}秒 <span v-if="config.ignoreLocal">/ 忽略局域网访问</span>
</span>
<span v-else>未启用</span>
<i class="icon angle" :class="{up: isEditing, down: !isEditing}"></i>
</a>
<table class="ui table selectable" v-show="isEditing">
<tr>
<td class="title">是否启用</td>
<td>
<checkbox v-model="config.isOn"></checkbox>
<p class="comment">启用后WAF将会尝试自动检测并阻止SYN Flood攻击。此功能需要节点已安装并启用Firewalld。</p>
</td>
</tr>
<tr>
<td>空连接次数</td>
<td>
<div class="ui input right labeled">
<input type="text" v-model="minAttempts" style="width: 5em" maxlength="4"/>
<span class="ui label">次/分钟</span>
</div>
<p class="comment">超过此数字的"空连接"将被视为SYN Flood攻击为了防止误判此数值默认不小于5。</p>
</td>
</tr>
<tr>
<td>封禁时间</td>
<td>
<div class="ui input right labeled">
<input type="text" v-model="timeoutSeconds" style="width: 5em" maxlength="4"/>
<span class="ui label">秒</span>
</div>
</td>
</tr>
<tr>
<td>忽略局域网访问</td>
<td>
<checkbox v-model="config.ignoreLocal"></checkbox>
</td>
</tr>
</table>
</div>`
})
// TODO 支持关键词搜索 // TODO 支持关键词搜索
// TODO 改成弹窗选择 // TODO 改成弹窗选择
Vue.component("admin-selector", { Vue.component("admin-selector", {
@@ -9858,6 +10197,18 @@ Vue.component("ip-list-table", {
.success(function () { .success(function () {
teaweb.successToast("批量删除成功", 1200, teaweb.reload) teaweb.successToast("批量删除成功", 1200, teaweb.reload)
}) })
},
formatSeconds: function (seconds) {
if (seconds < 60) {
return seconds + "秒"
}
if (seconds < 3600) {
return Math.ceil(seconds / 60) + "分钟"
}
if (seconds < 86400) {
return Math.ceil(seconds / 3600) + "小时"
}
return Math.ceil(seconds / 86400) + "天"
} }
}, },
template: `<div> template: `<div>
@@ -9891,7 +10242,7 @@ Vue.component("ip-list-table", {
</td> </td>
<td> <td>
<span v-if="item.type != 'all'"> <span v-if="item.type != 'all'">
<keyword :v-word="keyword">{{item.ipFrom}}</keyword> <span>&nbsp;<a :href="'/servers/iplists?ip=' + item.ipFrom" v-if="vShowSearchButton" title="搜索此IP"><span><i class="icon search small" style="color: #ccc"></i></span></a></span> <keyword :v-word="keyword">{{item.ipFrom}}</keyword> <span> <span class="small red" v-if="item.isRead != null && !item.isRead">&nbsp;New&nbsp;</span>&nbsp;<a :href="'/servers/iplists?ip=' + item.ipFrom" v-if="vShowSearchButton" title="搜索此IP"><span><i class="icon search small" style="color: #ccc"></i></span></a></span>
<span v-if="item.ipTo.length > 0"> - <keyword :v-word="keyword">{{item.ipTo}}</keyword></span></span> <span v-if="item.ipTo.length > 0"> - <keyword :v-word="keyword">{{item.ipTo}}</keyword></span></span>
<span v-else class="disabled">*</span> <span v-else class="disabled">*</span>
<div v-if="item.createdTime != null"> <div v-if="item.createdTime != null">
@@ -9931,6 +10282,9 @@ Vue.component("ip-list-table", {
<div v-if="item.isExpired" style="margin-top: 0.5em"> <div v-if="item.isExpired" style="margin-top: 0.5em">
<span class="ui label tiny basic red">已过期</span> <span class="ui label tiny basic red">已过期</span>
</div> </div>
<div v-if="item.lifeSeconds != null && item.lifeSeconds > 0">
<span class="small grey">{{formatSeconds(item.lifeSeconds)}}</span>
</div>
</div> </div>
<span v-else class="disabled">不过期</span> <span v-else class="disabled">不过期</span>
</td> </td>
@@ -9938,12 +10292,15 @@ Vue.component("ip-list-table", {
<span v-if="item.reason.length > 0">{{item.reason}}</span> <span v-if="item.reason.length > 0">{{item.reason}}</span>
<span v-else class="disabled">-</span> <span v-else class="disabled">-</span>
<div v-if="item.sourceNode != null && item.sourceNode.id > 0" style="margin-top: 0.4em">
<a :href="'/clusters/cluster/node?clusterId=' + item.sourceNode.clusterId + '&nodeId=' + item.sourceNode.id"><span class="small"><i class="icon cloud"></i>{{item.sourceNode.name}}</span></a>
</div>
<div style="margin-top: 0.4em" v-if="item.sourceServer != null && item.sourceServer.id > 0"> <div style="margin-top: 0.4em" v-if="item.sourceServer != null && item.sourceServer.id > 0">
<a :href="'/servers/server?serverId=' + item.sourceServer.id" style="border: 0"><span class="small "><i class="icon clone outline"></i>{{item.sourceServer.name}}</span></a> <a :href="'/servers/server?serverId=' + item.sourceServer.id" style="border: 0"><span class="small "><i class="icon clone outline"></i>{{item.sourceServer.name}}</span></a>
</div> </div>
<div v-if="item.sourcePolicy != null && item.sourcePolicy.id > 0" style="margin-top: 0.4em"> <div v-if="item.sourcePolicy != null && item.sourcePolicy.id > 0" style="margin-top: 0.4em">
<a :href="'/servers/components/waf/group?firewallPolicyId=' + item.sourcePolicy.id + '&type=inbound&groupId=' + item.sourceGroup.id" v-if="item.sourcePolicy.serverId == 0"><span class="small "><i class="icon shield"></i>{{item.sourcePolicy.name}} &raquo; {{item.sourceGroup.name}} &raquo; {{item.sourceSet.name}}</span></a> <a :href="'/servers/components/waf/group?firewallPolicyId=' + item.sourcePolicy.id + '&type=inbound&groupId=' + item.sourceGroup.id + '#set' + item.sourceSet.id" v-if="item.sourcePolicy.serverId == 0"><span class="small "><i class="icon shield"></i>{{item.sourcePolicy.name}} &raquo; {{item.sourceGroup.name}} &raquo; {{item.sourceSet.name}}</span></a>
<a :href="'/servers/server/settings/waf/group?serverId=' + item.sourcePolicy.serverId + '&firewallPolicyId=' + item.sourcePolicy.id + '&type=inbound&groupId=' + item.sourceGroup.id" v-if="item.sourcePolicy.serverId > 0"><span class="small "><i class="icon shield"></i> {{item.sourcePolicy.name}} &raquo; {{item.sourceGroup.name}} &raquo; {{item.sourceSet.name}}</span></a> <a :href="'/servers/server/settings/waf/group?serverId=' + item.sourcePolicy.serverId + '&firewallPolicyId=' + item.sourcePolicy.id + '&type=inbound&groupId=' + item.sourceGroup.id + '#set' + item.sourceSet.id" v-if="item.sourcePolicy.serverId > 0"><span class="small "><i class="icon shield"></i> {{item.sourcePolicy.name}} &raquo; {{item.sourceGroup.name}} &raquo; {{item.sourceSet.name}}</span></a>
</div> </div>
</td> </td>
<td> <td>
@@ -10866,7 +11223,17 @@ Vue.component("micro-basic-label", {
// 灰色的Label // 灰色的Label
Vue.component("grey-label", { Vue.component("grey-label", {
template: `<span class="ui label basic grey tiny" style="margin-top: 0.4em; font-size: 0.7em; border: 1px solid #ddd!important; font-weight: normal;"><slot></slot></span>` props: ["color"],
data: function () {
let color = "grey"
if (this.color != null && this.color.length > 0) {
color = "red"
}
return {
labelColor: color
}
},
template: `<span class="ui label basic tiny" :class="labelColor" style="margin-top: 0.4em; font-size: 0.7em; border: 1px solid #ddd!important; font-weight: normal;"><slot></slot></span>`
}) })
@@ -11312,6 +11679,166 @@ Vue.component("request-variables-describer", {
}) })
Vue.component("combo-box", {
props: ["name", "title", "placeholder", "size", "v-items", "v-value"],
data: function () {
let items = this.vItems
if (items == null || !(items instanceof Array)) {
items = []
}
// 自动使用ID作为值
items.forEach(function (v) {
if (v.value == null) {
v.value = v.id
}
})
// 当前选中项
let selectedItem = null
if (this.vValue != null) {
let that = this
items.forEach(function (v) {
if (v.value == that.vValue) {
selectedItem = v
}
})
}
return {
allItems: items,
items: items.$copy(),
selectedItem: selectedItem,
keyword: "",
visible: false,
hideTimer: null,
hoverIndex: 0
}
},
methods: {
reset: function () {
this.selectedItem = null
this.change()
this.hoverIndex = 0
let that = this
setTimeout(function () {
if (that.$refs.searchBox) {
that.$refs.searchBox.focus()
}
})
},
changeKeyword: function () {
this.hoverIndex = 0
let keyword = this.keyword
if (keyword.length == 0) {
this.items = this.allItems.$copy()
return
}
this.items = this.allItems.$copy().filter(function (v) {
return teaweb.match(v.name, keyword)
})
},
selectItem: function (item) {
this.selectedItem = item
this.change()
this.hoverIndex = 0
this.keyword = ""
this.changeKeyword()
},
confirm: function () {
if (this.items.length > this.hoverIndex) {
this.selectItem(this.items[this.hoverIndex])
}
},
show: function () {
this.visible = true
// 不要重置hoverIndex以便焦点可以在输入框和可选项之间切换
},
hide: function () {
let that = this
this.hideTimer = setTimeout(function () {
that.visible = false
}, 500)
},
downItem: function () {
this.hoverIndex++
if (this.hoverIndex > this.items.length - 1) {
this.hoverIndex = 0
}
this.focusItem()
},
upItem: function () {
this.hoverIndex--
if (this.hoverIndex < 0) {
this.hoverIndex = 0
}
this.focusItem()
},
focusItem: function () {
if (this.hoverIndex < this.items.length) {
this.$refs.itemRef[this.hoverIndex].focus()
let that = this
setTimeout(function () {
that.$refs.searchBox.focus()
if (that.hideTimer != null) {
clearTimeout(that.hideTimer)
that.hideTimer = null
}
})
}
},
change: function () {
this.$emit("change", this.selectedItem)
let that = this
setTimeout(function () {
if (that.$refs.selectedLabel != null) {
that.$refs.selectedLabel.focus()
}
})
},
submitForm: function (event) {
if (event.target.tagName != "A") {
return
}
let parentBox = this.$refs.selectedLabel.parentNode
while (true) {
parentBox = parentBox.parentNode
if (parentBox == null || parentBox.tagName == "BODY") {
return
}
if (parentBox.tagName == "FORM") {
parentBox.submit()
break
}
}
}
},
template: `<div style="display: inline; z-index: 10; background: white">
<!-- 搜索框 -->
<div v-if="selectedItem == null">
<input type="text" v-model="keyword" :placeholder="placeholder" :size="size" style="width: 11em" @input="changeKeyword" @focus="show" @blur="hide" @keyup.enter="confirm()" @keypress.enter.prevent="1" ref="searchBox" @keyup.down="downItem" @keyup.up="upItem"/>
</div>
<!-- 当前选中 -->
<div v-if="selectedItem != null">
<input type="hidden" :name="name" :value="selectedItem.value"/>
<a href="" class="ui label basic" ref="selectedLabel" @click.prevent="submitForm"><span>{{title}}{{selectedItem.name}}</span>
<span title="清除" @click.prevent="reset"><i class="icon remove small"></i></span>
</a>
</div>
<!-- 菜单 -->
<div v-if="selectedItem == null && items.length > 0 && visible">
<div class="ui menu vertical small narrow-scrollbar" style="width: 11em; max-height: 17em; overflow-y: auto; position: absolute; border: rgba(129, 177, 210, 0.81) 1px solid; border-top: 0; z-index: 100">
<a href="" v-for="(item, index) in items" ref="itemRef" class="item" :class="{active: index == hoverIndex, blue: index == hoverIndex}" @click.prevent="selectItem(item)" style="line-height: 1.4">{{item.name}}</a>
</div>
</div>
</div>`
})
Vue.component("time-duration-box", { Vue.component("time-duration-box", {
props: ["v-name", "v-value", "v-count", "v-unit"], props: ["v-name", "v-value", "v-count", "v-unit"],
mounted: function () { mounted: function () {
@@ -11725,6 +12252,19 @@ Vue.component("source-code-box", {
this.createEditor(box, value, readOnly) this.createEditor(box, value, readOnly)
}, },
data: function () {
let index = sourceCodeBoxIndex++
let valueBoxId = 'source-code-box-value-' + sourceCodeBoxIndex
if (this.id != null) {
valueBoxId = this.id
}
return {
index: index,
valueBoxId: valueBoxId
}
},
methods: { methods: {
createEditor: function (box, value, readOnly) { createEditor: function (box, value, readOnly) {
let boxEditor = CodeMirror.fromTextArea(box, { let boxEditor = CodeMirror.fromTextArea(box, {
@@ -11739,7 +12279,11 @@ Vue.component("source-code-box", {
lineWrapping: true, lineWrapping: true,
highlightFormatting: false, highlightFormatting: false,
indentUnit: 4, indentUnit: 4,
indentWithTabs: true indentWithTabs: true,
})
let that = this
boxEditor.on("change", function () {
that.change(boxEditor.getValue())
}) })
boxEditor.setValue(value) boxEditor.setValue(value)
@@ -11762,19 +12306,9 @@ Vue.component("source-code-box", {
CodeMirror.modeURL = "/codemirror/mode/%N/%N.js" CodeMirror.modeURL = "/codemirror/mode/%N/%N.js"
CodeMirror.autoLoadMode(boxEditor, info.mode) CodeMirror.autoLoadMode(boxEditor, info.mode)
} }
} },
}, change: function (code) {
data: function () { this.$emit("change", code)
let index = sourceCodeBoxIndex++
let valueBoxId = 'source-code-box-value-' + sourceCodeBoxIndex
if (this.id != null) {
valueBoxId = this.id
}
return {
index: index,
valueBoxId: valueBoxId
} }
}, },
template: `<div class="source-code-box"> template: `<div class="source-code-box">
@@ -13007,6 +13541,27 @@ Vue.component("node-region-selector", {
</div>` </div>`
}) })
Vue.component("node-combo-box", {
props: ["v-cluster-id", "v-node-id"],
data: function () {
let that = this
Tea.action("/clusters/nodeOptions")
.params({
clusterId: this.vClusterId
})
.post()
.success(function (resp) {
that.nodes = resp.data.nodes
})
return {
nodes: []
}
},
template: `<div v-if="nodes.length > 0">
<combo-box title="节点" placeholder="节点名称" :v-items="nodes" name="nodeId" :v-value="vNodeId"></combo-box>
</div>`
})
Vue.component("dns-route-selector", { Vue.component("dns-route-selector", {
props: ["v-all-routes", "v-routes"], props: ["v-all-routes", "v-routes"],
data: function () { data: function () {
@@ -13274,7 +13829,7 @@ window.REQUEST_COND_COMPONENTS = [{"type":"url-extension","name":"URL扩展名",
window.REQUEST_COND_OPERATORS = [{"description":"判断是否正则表达式匹配","name":"正则表达式匹配","op":"regexp"},{"description":"判断是否正则表达式不匹配","name":"正则表达式不匹配","op":"not regexp"},{"description":"使用字符串对比参数值是否相等于某个值","name":"字符串等于","op":"eq"},{"description":"参数值包含某个前缀","name":"字符串前缀","op":"prefix"},{"description":"参数值包含某个后缀","name":"字符串后缀","op":"suffix"},{"description":"参数值包含另外一个字符串","name":"字符串包含","op":"contains"},{"description":"参数值不包含另外一个字符串","name":"字符串不包含","op":"not contains"},{"description":"使用字符串对比参数值是否不相等于某个值","name":"字符串不等于","op":"not"},{"description":"判断参数值在某个列表中","name":"在列表中","op":"in"},{"description":"判断参数值不在某个列表中","name":"不在列表中","op":"not in"},{"description":"判断小写的扩展名(不带点)在某个列表中","name":"扩展名","op":"file ext"},{"description":"判断MimeType在某个列表中支持类似于image/*的语法","name":"MimeType","op":"mime type"},{"description":"判断版本号在某个范围内格式为version1,version2","name":"版本号范围","op":"version range"},{"description":"将参数转换为整数数字后进行对比","name":"整数等于","op":"eq int"},{"description":"将参数转换为可以有小数的浮点数字进行对比","name":"浮点数等于","op":"eq float"},{"description":"将参数转换为数字进行对比","name":"数字大于","op":"gt"},{"description":"将参数转换为数字进行对比","name":"数字大于等于","op":"gte"},{"description":"将参数转换为数字进行对比","name":"数字小于","op":"lt"},{"description":"将参数转换为数字进行对比","name":"数字小于等于","op":"lte"},{"description":"对整数参数值取模除数为10对比值为余数","name":"整数取模10","op":"mod 10"},{"description":"对整数参数值取模除数为100对比值为余数","name":"整数取模100","op":"mod 100"},{"description":"对整数参数值取模,对比值格式为:除数,余数比如10,1","name":"整数取模","op":"mod"},{"description":"将参数转换为IP进行对比","name":"IP等于","op":"eq ip"},{"description":"将参数转换为IP进行对比","name":"IP大于","op":"gt ip"},{"description":"将参数转换为IP进行对比","name":"IP大于等于","op":"gte ip"},{"description":"将参数转换为IP进行对比","name":"IP小于","op":"lt ip"},{"description":"将参数转换为IP进行对比","name":"IP小于等于","op":"lte ip"},{"description":"IP在某个范围之内范围格式可以是英文逗号分隔的ip1,ip2或者CIDR格式的ip/bits","name":"IP范围","op":"ip range"},{"description":"对IP参数值取模除数为10对比值为余数","name":"IP取模10","op":"ip mod 10"},{"description":"对IP参数值取模除数为100对比值为余数","name":"IP取模100","op":"ip mod 100"},{"description":"对IP参数值取模对比值格式为除数,余数比如10,1","name":"IP取模","op":"ip mod"},{"description":"判断参数值解析后的文件是否存在","name":"文件存在","op":"file exist"},{"description":"判断参数值解析后的文件是否不存在","name":"文件不存在","op":"file not exist"}] window.REQUEST_COND_OPERATORS = [{"description":"判断是否正则表达式匹配","name":"正则表达式匹配","op":"regexp"},{"description":"判断是否正则表达式不匹配","name":"正则表达式不匹配","op":"not regexp"},{"description":"使用字符串对比参数值是否相等于某个值","name":"字符串等于","op":"eq"},{"description":"参数值包含某个前缀","name":"字符串前缀","op":"prefix"},{"description":"参数值包含某个后缀","name":"字符串后缀","op":"suffix"},{"description":"参数值包含另外一个字符串","name":"字符串包含","op":"contains"},{"description":"参数值不包含另外一个字符串","name":"字符串不包含","op":"not contains"},{"description":"使用字符串对比参数值是否不相等于某个值","name":"字符串不等于","op":"not"},{"description":"判断参数值在某个列表中","name":"在列表中","op":"in"},{"description":"判断参数值不在某个列表中","name":"不在列表中","op":"not in"},{"description":"判断小写的扩展名(不带点)在某个列表中","name":"扩展名","op":"file ext"},{"description":"判断MimeType在某个列表中支持类似于image/*的语法","name":"MimeType","op":"mime type"},{"description":"判断版本号在某个范围内格式为version1,version2","name":"版本号范围","op":"version range"},{"description":"将参数转换为整数数字后进行对比","name":"整数等于","op":"eq int"},{"description":"将参数转换为可以有小数的浮点数字进行对比","name":"浮点数等于","op":"eq float"},{"description":"将参数转换为数字进行对比","name":"数字大于","op":"gt"},{"description":"将参数转换为数字进行对比","name":"数字大于等于","op":"gte"},{"description":"将参数转换为数字进行对比","name":"数字小于","op":"lt"},{"description":"将参数转换为数字进行对比","name":"数字小于等于","op":"lte"},{"description":"对整数参数值取模除数为10对比值为余数","name":"整数取模10","op":"mod 10"},{"description":"对整数参数值取模除数为100对比值为余数","name":"整数取模100","op":"mod 100"},{"description":"对整数参数值取模,对比值格式为:除数,余数比如10,1","name":"整数取模","op":"mod"},{"description":"将参数转换为IP进行对比","name":"IP等于","op":"eq ip"},{"description":"将参数转换为IP进行对比","name":"IP大于","op":"gt ip"},{"description":"将参数转换为IP进行对比","name":"IP大于等于","op":"gte ip"},{"description":"将参数转换为IP进行对比","name":"IP小于","op":"lt ip"},{"description":"将参数转换为IP进行对比","name":"IP小于等于","op":"lte ip"},{"description":"IP在某个范围之内范围格式可以是英文逗号分隔的ip1,ip2或者CIDR格式的ip/bits","name":"IP范围","op":"ip range"},{"description":"对IP参数值取模除数为10对比值为余数","name":"IP取模10","op":"ip mod 10"},{"description":"对IP参数值取模除数为100对比值为余数","name":"IP取模100","op":"ip mod 100"},{"description":"对IP参数值取模对比值格式为除数,余数比如10,1","name":"IP取模","op":"ip mod"},{"description":"判断参数值解析后的文件是否存在","name":"文件存在","op":"file exist"},{"description":"判断参数值解析后的文件是否不存在","name":"文件不存在","op":"file not exist"}]
window.REQUEST_VARIABLES = [{"code":"${edgeVersion}","description":"","name":"边缘节点版本"},{"code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取适合前端有别的反向代理服务时使用存在伪造的风险","name":"客户端地址IP"},{"code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","name":"客户端地址IP"},{"code":"${remotePort}","description":"","name":"客户端端口"},{"code":"${remoteUser}","description":"","name":"客户端用户名"},{"code":"${requestURI}","description":"比如/hello?name=lily","name":"请求URI"},{"code":"${requestPath}","description":"比如/hello","name":"请求路径(不包括参数)"},{"code":"${requestURL}","description":"比如https://example.com/hello?name=lily","name":"完整的请求URL"},{"code":"${requestLength}","description":"","name":"请求内容长度"},{"code":"${requestMethod}","description":"比如GET、POST","name":"请求方法"},{"code":"${requestFilename}","description":"","name":"请求文件路径"},{"code":"${scheme}","description":"","name":"请求协议http或https"},{"code":"${proto}","description:":"类似于HTTP/1.0","name":"包含版本的HTTP请求协议"},{"code":"${timeISO8601}","description":"比如2018-07-16T23:52:24.839+08:00","name":"ISO 8601格式的时间"},{"code":"${timeLocal}","description":"比如17/Jul/2018:09:52:24 +0800","name":"本地时间"},{"code":"${msec}","description":"比如1531756823.054","name":"带有毫秒的时间"},{"code":"${timestamp}","description":"","name":"unix时间戳单位为秒"},{"code":"${host}","description":"","name":"主机名"},{"code":"${serverName}","description":"","name":"接收请求的服务器名"},{"code":"${serverPort}","description":"","name":"接收请求的服务器端口"},{"code":"${referer}","description":"","name":"请求来源URL"},{"code":"${referer.host}","description":"","name":"请求来源URL域名"},{"code":"${userAgent}","description":"","name":"客户端信息"},{"code":"${contentType}","description":"","name":"请求头部的Content-Type"},{"code":"${cookies}","description":"","name":"所有cookie组合字符串"},{"code":"${cookie.NAME}","description":"","name":"单个cookie值"},{"code":"${isArgs}","description":"如果URL有参数则值为`?`;否则,则值为空","name":"问号(?)标记"},{"code":"${args}","description":"","name":"所有参数组合字符串"},{"code":"${arg.NAME}","description":"","name":"单个参数值"},{"code":"${headers}","description":"","name":"所有Header信息组合字符串"},{"code":"${header.NAME}","description":"","name":"单个Header值"}] window.REQUEST_VARIABLES = [{"code":"${edgeVersion}","description":"","name":"边缘节点版本"},{"code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取适合前端有别的反向代理服务时使用存在伪造的风险","name":"客户端地址IP"},{"code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","name":"客户端地址IP"},{"code":"${remotePort}","description":"","name":"客户端端口"},{"code":"${remoteUser}","description":"","name":"客户端用户名"},{"code":"${requestURI}","description":"比如/hello?name=lily","name":"请求URI"},{"code":"${requestPath}","description":"比如/hello","name":"请求路径(不包括参数)"},{"code":"${requestURL}","description":"比如https://example.com/hello?name=lily","name":"完整的请求URL"},{"code":"${requestLength}","description":"","name":"请求内容长度"},{"code":"${requestMethod}","description":"比如GET、POST","name":"请求方法"},{"code":"${requestFilename}","description":"","name":"请求文件路径"},{"code":"${scheme}","description":"","name":"请求协议http或https"},{"code":"${proto}","description:":"类似于HTTP/1.0","name":"包含版本的HTTP请求协议"},{"code":"${timeISO8601}","description":"比如2018-07-16T23:52:24.839+08:00","name":"ISO 8601格式的时间"},{"code":"${timeLocal}","description":"比如17/Jul/2018:09:52:24 +0800","name":"本地时间"},{"code":"${msec}","description":"比如1531756823.054","name":"带有毫秒的时间"},{"code":"${timestamp}","description":"","name":"unix时间戳单位为秒"},{"code":"${host}","description":"","name":"主机名"},{"code":"${serverName}","description":"","name":"接收请求的服务器名"},{"code":"${serverPort}","description":"","name":"接收请求的服务器端口"},{"code":"${referer}","description":"","name":"请求来源URL"},{"code":"${referer.host}","description":"","name":"请求来源URL域名"},{"code":"${userAgent}","description":"","name":"客户端信息"},{"code":"${contentType}","description":"","name":"请求头部的Content-Type"},{"code":"${cookies}","description":"","name":"所有cookie组合字符串"},{"code":"${cookie.NAME}","description":"","name":"单个cookie值"},{"code":"${isArgs}","description":"如果URL有参数则值为`?`;否则,则值为空","name":"问号(?)标记"},{"code":"${args}","description":"","name":"所有参数组合字符串"},{"code":"${arg.NAME}","description":"","name":"单个参数值"},{"code":"${headers}","description":"","name":"所有Header信息组合字符串"},{"code":"${header.NAME}","description":"","name":"单个Header值"},{"code":"${geo.country.name}","description":"","name":"国家/地区名称"},{"code":"${geo.country.id}","description":"","name":"国家/地区ID"},{"code":"${geo.province.name}","description":"目前只包含中国省份","name":"省份名称"},{"code":"${geo.province.id}","description":"目前只包含中国省份","name":"省份ID"},{"code":"${geo.city.name}","description":"目前只包含中国城市","name":"城市名称"},{"code":"${geo.city.id}","description":"目前只包含中国城市","name":"城市名称"},{"code":"${isp.name}","description":"","name":"ISP服务商名称"},{"code":"${isp.id}","description":"","name":"ISP服务商ID"},{"code":"${browser.os.name}","description":"客户端所在操作系统名称","name":"操作系统名称"},{"code":"${browser.os.version}","description":"客户端所在操作系统版本","name":"操作系统版本"},{"code":"${browser.name}","description":"客户端浏览器名称","name":"浏览器名称"},{"code":"${browser.version}","description":"客户端浏览器版本","name":"浏览器版本"},{"code":"${browser.isMobile}","description":"如果客户端是手机则值为1否则为0","name":"手机标识"}]
window.METRIC_HTTP_KEYS = [{"name":"客户端地址IP","code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取适用于前端可能有别的反向代理的情形存在被伪造的可能","icon":""},{"name":"直接客户端地址IP","code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","icon":""},{"name":"客户端用户名","code":"${remoteUser}","description":"通过基本认证填入的用户名","icon":""},{"name":"请求URI","code":"${requestURI}","description":"包含参数,比如/hello?name=lily","icon":""},{"name":"请求路径","code":"${requestPath}","description":"不包含参数,比如/hello","icon":""},{"name":"完整URL","code":"${requestURL}","description":"比如https://example.com/hello?name=lily","icon":""},{"name":"请求方法","code":"${requestMethod}","description":"比如GET、POST等","icon":""},{"name":"请求协议Scheme","code":"${scheme}","description":"http或https","icon":""},{"name":"文件扩展名","code":"${requestPathExtension}","description":"请求路径中的文件扩展名,包括点符号,比如.html、.png","icon":""},{"name":"主机名","code":"${host}","description":"通常是请求的域名","icon":""},{"name":"请求协议Proto","code":"${proto}","description":"包含版本的HTTP请求协议类似于HTTP/1.0","icon":""},{"name":"HTTP协议","code":"${proto}","description":"包含版本的HTTP请求协议类似于HTTP/1.0","icon":""},{"name":"URL参数值","code":"${arg.NAME}","description":"单个URL参数值","icon":""},{"name":"请求来源URL","code":"${referer}","description":"请求来源Referer URL","icon":""},{"name":"请求来源URL域名","code":"${referer.host}","description":"请求来源Referer URL域名","icon":""},{"name":"Header值","code":"${header.NAME}","description":"单个Header值比如${header.User-Agent}","icon":""},{"name":"Cookie值","code":"${cookie.NAME}","description":"单个cookie值比如${cookie.sid}","icon":""},{"name":"状态码","code":"${status}","description":"","icon":""},{"name":"响应的Content-Type值","code":"${response.contentType}","description":"","icon":""}] window.METRIC_HTTP_KEYS = [{"name":"客户端地址IP","code":"${remoteAddr}","description":"会依次根据X-Forwarded-For、X-Real-IP、RemoteAddr获取适用于前端可能有别的反向代理的情形存在被伪造的可能","icon":""},{"name":"直接客户端地址IP","code":"${rawRemoteAddr}","description":"返回直接连接服务的客户端原始IP地址","icon":""},{"name":"客户端用户名","code":"${remoteUser}","description":"通过基本认证填入的用户名","icon":""},{"name":"请求URI","code":"${requestURI}","description":"包含参数,比如/hello?name=lily","icon":""},{"name":"请求路径","code":"${requestPath}","description":"不包含参数,比如/hello","icon":""},{"name":"完整URL","code":"${requestURL}","description":"比如https://example.com/hello?name=lily","icon":""},{"name":"请求方法","code":"${requestMethod}","description":"比如GET、POST等","icon":""},{"name":"请求协议Scheme","code":"${scheme}","description":"http或https","icon":""},{"name":"文件扩展名","code":"${requestPathExtension}","description":"请求路径中的文件扩展名,包括点符号,比如.html、.png","icon":""},{"name":"主机名","code":"${host}","description":"通常是请求的域名","icon":""},{"name":"请求协议Proto","code":"${proto}","description":"包含版本的HTTP请求协议类似于HTTP/1.0","icon":""},{"name":"HTTP协议","code":"${proto}","description":"包含版本的HTTP请求协议类似于HTTP/1.0","icon":""},{"name":"URL参数值","code":"${arg.NAME}","description":"单个URL参数值","icon":""},{"name":"请求来源URL","code":"${referer}","description":"请求来源Referer URL","icon":""},{"name":"请求来源URL域名","code":"${referer.host}","description":"请求来源Referer URL域名","icon":""},{"name":"Header值","code":"${header.NAME}","description":"单个Header值比如${header.User-Agent}","icon":""},{"name":"Cookie值","code":"${cookie.NAME}","description":"单个cookie值比如${cookie.sid}","icon":""},{"name":"状态码","code":"${status}","description":"","icon":""},{"name":"响应的Content-Type值","code":"${response.contentType}","description":"","icon":""}]

View File

@@ -0,0 +1,26 @@
Vue.component("node-cluster-combo-box", {
props: ["v-cluster-id"],
data: function () {
let that = this
Tea.action("/clusters/options")
.post()
.success(function (resp) {
that.clusters = resp.data.clusters
})
return {
clusters: []
}
},
methods: {
change: function (item) {
if (item == null) {
this.$emit("change", 0)
} else {
this.$emit("change", item.value)
}
}
},
template: `<div v-if="clusters.length > 0">
<combo-box title="集群" placeholder="集群名称" :v-items="clusters" name="clusterId" :v-value="vClusterId" @change="change"></combo-box>
</div>`
})

View File

@@ -0,0 +1,159 @@
Vue.component("combo-box", {
props: ["name", "title", "placeholder", "size", "v-items", "v-value"],
data: function () {
let items = this.vItems
if (items == null || !(items instanceof Array)) {
items = []
}
// 自动使用ID作为值
items.forEach(function (v) {
if (v.value == null) {
v.value = v.id
}
})
// 当前选中项
let selectedItem = null
if (this.vValue != null) {
let that = this
items.forEach(function (v) {
if (v.value == that.vValue) {
selectedItem = v
}
})
}
return {
allItems: items,
items: items.$copy(),
selectedItem: selectedItem,
keyword: "",
visible: false,
hideTimer: null,
hoverIndex: 0
}
},
methods: {
reset: function () {
this.selectedItem = null
this.change()
this.hoverIndex = 0
let that = this
setTimeout(function () {
if (that.$refs.searchBox) {
that.$refs.searchBox.focus()
}
})
},
changeKeyword: function () {
this.hoverIndex = 0
let keyword = this.keyword
if (keyword.length == 0) {
this.items = this.allItems.$copy()
return
}
this.items = this.allItems.$copy().filter(function (v) {
return teaweb.match(v.name, keyword)
})
},
selectItem: function (item) {
this.selectedItem = item
this.change()
this.hoverIndex = 0
this.keyword = ""
this.changeKeyword()
},
confirm: function () {
if (this.items.length > this.hoverIndex) {
this.selectItem(this.items[this.hoverIndex])
}
},
show: function () {
this.visible = true
// 不要重置hoverIndex以便焦点可以在输入框和可选项之间切换
},
hide: function () {
let that = this
this.hideTimer = setTimeout(function () {
that.visible = false
}, 500)
},
downItem: function () {
this.hoverIndex++
if (this.hoverIndex > this.items.length - 1) {
this.hoverIndex = 0
}
this.focusItem()
},
upItem: function () {
this.hoverIndex--
if (this.hoverIndex < 0) {
this.hoverIndex = 0
}
this.focusItem()
},
focusItem: function () {
if (this.hoverIndex < this.items.length) {
this.$refs.itemRef[this.hoverIndex].focus()
let that = this
setTimeout(function () {
that.$refs.searchBox.focus()
if (that.hideTimer != null) {
clearTimeout(that.hideTimer)
that.hideTimer = null
}
})
}
},
change: function () {
this.$emit("change", this.selectedItem)
let that = this
setTimeout(function () {
if (that.$refs.selectedLabel != null) {
that.$refs.selectedLabel.focus()
}
})
},
submitForm: function (event) {
if (event.target.tagName != "A") {
return
}
let parentBox = this.$refs.selectedLabel.parentNode
while (true) {
parentBox = parentBox.parentNode
if (parentBox == null || parentBox.tagName == "BODY") {
return
}
if (parentBox.tagName == "FORM") {
parentBox.submit()
break
}
}
}
},
template: `<div style="display: inline; z-index: 10; background: white">
<!-- 搜索框 -->
<div v-if="selectedItem == null">
<input type="text" v-model="keyword" :placeholder="placeholder" :size="size" style="width: 11em" @input="changeKeyword" @focus="show" @blur="hide" @keyup.enter="confirm()" @keypress.enter.prevent="1" ref="searchBox" @keyup.down="downItem" @keyup.up="upItem"/>
</div>
<!-- 当前选中 -->
<div v-if="selectedItem != null">
<input type="hidden" :name="name" :value="selectedItem.value"/>
<a href="" class="ui label basic" ref="selectedLabel" @click.prevent="submitForm"><span>{{title}}{{selectedItem.name}}</span>
<span title="清除" @click.prevent="reset"><i class="icon remove small"></i></span>
</a>
</div>
<!-- 菜单 -->
<div v-if="selectedItem == null && items.length > 0 && visible">
<div class="ui menu vertical small narrow-scrollbar" style="width: 11em; max-height: 17em; overflow-y: auto; position: absolute; border: rgba(129, 177, 210, 0.81) 1px solid; border-top: 0; z-index: 100">
<a href="" v-for="(item, index) in items" ref="itemRef" class="item" :class="{active: index == hoverIndex, blue: index == hoverIndex}" @click.prevent="selectItem(item)" style="line-height: 1.4">{{item.name}}</a>
</div>
</div>
</div>`
})

View File

@@ -31,5 +31,15 @@ Vue.component("micro-basic-label", {
// 灰色的Label // 灰色的Label
Vue.component("grey-label", { Vue.component("grey-label", {
template: `<span class="ui label basic grey tiny" style="margin-top: 0.4em; font-size: 0.7em; border: 1px solid #ddd!important; font-weight: normal;"><slot></slot></span>` props: ["color"],
data: function () {
let color = "grey"
if (this.color != null && this.color.length > 0) {
color = "red"
}
return {
labelColor: color
}
},
template: `<span class="ui label basic tiny" :class="labelColor" style="margin-top: 0.4em; font-size: 0.7em; border: 1px solid #ddd!important; font-weight: normal;"><slot></slot></span>`
}) })

View File

@@ -18,6 +18,19 @@ Vue.component("source-code-box", {
this.createEditor(box, value, readOnly) this.createEditor(box, value, readOnly)
}, },
data: function () {
let index = sourceCodeBoxIndex++
let valueBoxId = 'source-code-box-value-' + sourceCodeBoxIndex
if (this.id != null) {
valueBoxId = this.id
}
return {
index: index,
valueBoxId: valueBoxId
}
},
methods: { methods: {
createEditor: function (box, value, readOnly) { createEditor: function (box, value, readOnly) {
let boxEditor = CodeMirror.fromTextArea(box, { let boxEditor = CodeMirror.fromTextArea(box, {
@@ -32,7 +45,11 @@ Vue.component("source-code-box", {
lineWrapping: true, lineWrapping: true,
highlightFormatting: false, highlightFormatting: false,
indentUnit: 4, indentUnit: 4,
indentWithTabs: true indentWithTabs: true,
})
let that = this
boxEditor.on("change", function () {
that.change(boxEditor.getValue())
}) })
boxEditor.setValue(value) boxEditor.setValue(value)
@@ -55,19 +72,9 @@ Vue.component("source-code-box", {
CodeMirror.modeURL = "/codemirror/mode/%N/%N.js" CodeMirror.modeURL = "/codemirror/mode/%N/%N.js"
CodeMirror.autoLoadMode(boxEditor, info.mode) CodeMirror.autoLoadMode(boxEditor, info.mode)
} }
} },
}, change: function (code) {
data: function () { this.$emit("change", code)
let index = sourceCodeBoxIndex++
let valueBoxId = 'source-code-box-value-' + sourceCodeBoxIndex
if (this.id != null) {
valueBoxId = this.id
}
return {
index: index,
valueBoxId: valueBoxId
} }
}, },
template: `<div class="source-code-box"> template: `<div class="source-code-box">

View File

@@ -70,6 +70,18 @@ Vue.component("ip-list-table", {
.success(function () { .success(function () {
teaweb.successToast("批量删除成功", 1200, teaweb.reload) teaweb.successToast("批量删除成功", 1200, teaweb.reload)
}) })
},
formatSeconds: function (seconds) {
if (seconds < 60) {
return seconds + "秒"
}
if (seconds < 3600) {
return Math.ceil(seconds / 60) + "分钟"
}
if (seconds < 86400) {
return Math.ceil(seconds / 3600) + "小时"
}
return Math.ceil(seconds / 86400) + "天"
} }
}, },
template: `<div> template: `<div>
@@ -103,7 +115,7 @@ Vue.component("ip-list-table", {
</td> </td>
<td> <td>
<span v-if="item.type != 'all'"> <span v-if="item.type != 'all'">
<keyword :v-word="keyword">{{item.ipFrom}}</keyword> <span>&nbsp;<a :href="'/servers/iplists?ip=' + item.ipFrom" v-if="vShowSearchButton" title="搜索此IP"><span><i class="icon search small" style="color: #ccc"></i></span></a></span> <keyword :v-word="keyword">{{item.ipFrom}}</keyword> <span> <span class="small red" v-if="item.isRead != null && !item.isRead">&nbsp;New&nbsp;</span>&nbsp;<a :href="'/servers/iplists?ip=' + item.ipFrom" v-if="vShowSearchButton" title="搜索此IP"><span><i class="icon search small" style="color: #ccc"></i></span></a></span>
<span v-if="item.ipTo.length > 0"> - <keyword :v-word="keyword">{{item.ipTo}}</keyword></span></span> <span v-if="item.ipTo.length > 0"> - <keyword :v-word="keyword">{{item.ipTo}}</keyword></span></span>
<span v-else class="disabled">*</span> <span v-else class="disabled">*</span>
<div v-if="item.createdTime != null"> <div v-if="item.createdTime != null">
@@ -143,6 +155,9 @@ Vue.component("ip-list-table", {
<div v-if="item.isExpired" style="margin-top: 0.5em"> <div v-if="item.isExpired" style="margin-top: 0.5em">
<span class="ui label tiny basic red">已过期</span> <span class="ui label tiny basic red">已过期</span>
</div> </div>
<div v-if="item.lifeSeconds != null && item.lifeSeconds > 0">
<span class="small grey">{{formatSeconds(item.lifeSeconds)}}</span>
</div>
</div> </div>
<span v-else class="disabled">不过期</span> <span v-else class="disabled">不过期</span>
</td> </td>
@@ -150,12 +165,15 @@ Vue.component("ip-list-table", {
<span v-if="item.reason.length > 0">{{item.reason}}</span> <span v-if="item.reason.length > 0">{{item.reason}}</span>
<span v-else class="disabled">-</span> <span v-else class="disabled">-</span>
<div v-if="item.sourceNode != null && item.sourceNode.id > 0" style="margin-top: 0.4em">
<a :href="'/clusters/cluster/node?clusterId=' + item.sourceNode.clusterId + '&nodeId=' + item.sourceNode.id"><span class="small"><i class="icon cloud"></i>{{item.sourceNode.name}}</span></a>
</div>
<div style="margin-top: 0.4em" v-if="item.sourceServer != null && item.sourceServer.id > 0"> <div style="margin-top: 0.4em" v-if="item.sourceServer != null && item.sourceServer.id > 0">
<a :href="'/servers/server?serverId=' + item.sourceServer.id" style="border: 0"><span class="small "><i class="icon clone outline"></i>{{item.sourceServer.name}}</span></a> <a :href="'/servers/server?serverId=' + item.sourceServer.id" style="border: 0"><span class="small "><i class="icon clone outline"></i>{{item.sourceServer.name}}</span></a>
</div> </div>
<div v-if="item.sourcePolicy != null && item.sourcePolicy.id > 0" style="margin-top: 0.4em"> <div v-if="item.sourcePolicy != null && item.sourcePolicy.id > 0" style="margin-top: 0.4em">
<a :href="'/servers/components/waf/group?firewallPolicyId=' + item.sourcePolicy.id + '&type=inbound&groupId=' + item.sourceGroup.id" v-if="item.sourcePolicy.serverId == 0"><span class="small "><i class="icon shield"></i>{{item.sourcePolicy.name}} &raquo; {{item.sourceGroup.name}} &raquo; {{item.sourceSet.name}}</span></a> <a :href="'/servers/components/waf/group?firewallPolicyId=' + item.sourcePolicy.id + '&type=inbound&groupId=' + item.sourceGroup.id + '#set' + item.sourceSet.id" v-if="item.sourcePolicy.serverId == 0"><span class="small "><i class="icon shield"></i>{{item.sourcePolicy.name}} &raquo; {{item.sourceGroup.name}} &raquo; {{item.sourceSet.name}}</span></a>
<a :href="'/servers/server/settings/waf/group?serverId=' + item.sourcePolicy.serverId + '&firewallPolicyId=' + item.sourcePolicy.id + '&type=inbound&groupId=' + item.sourceGroup.id" v-if="item.sourcePolicy.serverId > 0"><span class="small "><i class="icon shield"></i> {{item.sourcePolicy.name}} &raquo; {{item.sourceGroup.name}} &raquo; {{item.sourceSet.name}}</span></a> <a :href="'/servers/server/settings/waf/group?serverId=' + item.sourcePolicy.serverId + '&firewallPolicyId=' + item.sourcePolicy.id + '&type=inbound&groupId=' + item.sourceGroup.id + '#set' + item.sourceSet.id" v-if="item.sourcePolicy.serverId > 0"><span class="small "><i class="icon shield"></i> {{item.sourcePolicy.name}} &raquo; {{item.sourceGroup.name}} &raquo; {{item.sourceSet.name}}</span></a>
</div> </div>
</td> </td>
<td> <td>

View File

@@ -0,0 +1,20 @@
Vue.component("node-combo-box", {
props: ["v-cluster-id", "v-node-id"],
data: function () {
let that = this
Tea.action("/clusters/nodeOptions")
.params({
clusterId: this.vClusterId
})
.post()
.success(function (resp) {
that.nodes = resp.data.nodes
})
return {
nodes: []
}
},
template: `<div v-if="nodes.length > 0">
<combo-box title="节点" placeholder="节点名称" :v-items="nodes" name="nodeId" :v-value="vNodeId"></combo-box>
</div>`
})

View File

@@ -0,0 +1,92 @@
Vue.component("firewall-syn-flood-config-box", {
props: ["v-syn-flood-config"],
data: function () {
let config = this.vSynFloodConfig
if (config == null) {
config = {
isOn: false,
minAttempts: 10,
timeoutSeconds: 600,
ignoreLocal: true
}
}
return {
config: config,
isEditing: false,
minAttempts: config.minAttempts,
timeoutSeconds: config.timeoutSeconds
}
},
methods: {
edit: function () {
this.isEditing = !this.isEditing
}
},
watch: {
minAttempts: function (v) {
let count = parseInt(v)
if (isNaN(count)) {
count = 10
}
if (count < 5) {
count = 5
}
this.config.minAttempts = count
},
timeoutSeconds: function (v) {
let seconds = parseInt(v)
if (isNaN(seconds)) {
seconds = 10
}
if (seconds < 60) {
seconds = 60
}
this.config.timeoutSeconds = seconds
}
},
template: `<div>
<input type="hidden" name="synFloodJSON" :value="JSON.stringify(config)"/>
<a href="" @click.prevent="edit">
<span v-if="config.isOn">
已启用 / <span>空连接次数:{{config.minAttempts}}次/分钟</span> / 封禁时间:{{config.timeoutSeconds}}秒 <span v-if="config.ignoreLocal">/ 忽略局域网访问</span>
</span>
<span v-else>未启用</span>
<i class="icon angle" :class="{up: isEditing, down: !isEditing}"></i>
</a>
<table class="ui table selectable" v-show="isEditing">
<tr>
<td class="title">是否启用</td>
<td>
<checkbox v-model="config.isOn"></checkbox>
<p class="comment">启用后WAF将会尝试自动检测并阻止SYN Flood攻击。此功能需要节点已安装并启用Firewalld。</p>
</td>
</tr>
<tr>
<td>空连接次数</td>
<td>
<div class="ui input right labeled">
<input type="text" v-model="minAttempts" style="width: 5em" maxlength="4"/>
<span class="ui label">次/分钟</span>
</div>
<p class="comment">超过此数字的"空连接"将被视为SYN Flood攻击为了防止误判此数值默认不小于5。</p>
</td>
</tr>
<tr>
<td>封禁时间</td>
<td>
<div class="ui input right labeled">
<input type="text" v-model="timeoutSeconds" style="width: 5em" maxlength="4"/>
<span class="ui label">秒</span>
</div>
</td>
</tr>
<tr>
<td>忽略局域网访问</td>
<td>
<checkbox v-model="config.ignoreLocal"></checkbox>
</td>
</tr>
</table>
</div>`
})

View File

@@ -1,6 +1,6 @@
// 访问日志搜索框 // 访问日志搜索框
Vue.component("http-access-log-search-box", { Vue.component("http-access-log-search-box", {
props: ["v-ip", "v-domain", "v-keyword"], props: ["v-ip", "v-domain", "v-keyword", "v-cluster-id", "v-node-id"],
data: function () { data: function () {
let ip = this.vIp let ip = this.vIp
if (ip == null) { if (ip == null) {
@@ -20,7 +20,8 @@ Vue.component("http-access-log-search-box", {
return { return {
ip: ip, ip: ip,
domain: domain, domain: domain,
keyword: keyword keyword: keyword,
clusterId: this.vClusterId
} }
}, },
methods: { methods: {
@@ -52,9 +53,12 @@ Vue.component("http-access-log-search-box", {
parent.submit() parent.submit()
}, 500) }, 500)
} }
},
changeCluster: function (clusterId) {
this.clusterId = clusterId
} }
}, },
template: `<div> template: `<div style="z-index: 10">
<div class="margin"></div> <div class="margin"></div>
<div class="ui fields inline"> <div class="ui fields inline">
<div class="ui field"> <div class="ui field">
@@ -79,8 +83,16 @@ Vue.component("http-access-log-search-box", {
</div> </div>
</div> </div>
<slot></slot> <slot></slot>
</div>
<div class="ui fields inline" style="margin-top: 0.5em">
<div class="ui field"> <div class="ui field">
<button class="ui button small" type="submit">查找</button> <node-cluster-combo-box :v-cluster-id="clusterId" @change="changeCluster"></node-cluster-combo-box>
</div>
<div class="ui field" v-if="clusterId > 0">
<node-combo-box :v-cluster-id="clusterId" :v-node-id="vNodeId"></node-combo-box>
</div>
<div class="ui field">
<button class="ui button small" type="submit">搜索日志</button>
</div> </div>
</div> </div>
</div>` </div>`

View File

@@ -193,7 +193,7 @@ Vue.component("http-cache-refs-config-box", {
</table> </table>
<p class="comment" v-if="refs.length > 1">所有条件匹配顺序为从上到下,可以拖动左侧的<i class="icon bars"></i>排序。服务设置的优先级比全局缓存策略设置的优先级要高。</p> <p class="comment" v-if="refs.length > 1">所有条件匹配顺序为从上到下,可以拖动左侧的<i class="icon bars"></i>排序。服务设置的优先级比全局缓存策略设置的优先级要高。</p>
<button class="ui button tiny" @click.prevent="addRef(false)">+添加缓存设置</button> &nbsp; &nbsp; <a href="" @click.prevent="addRef(true)">+添加不缓存设置</a> <button class="ui button tiny" @click.prevent="addRef(false)" type="button">+添加缓存设置</button> &nbsp; &nbsp; <a href="" @click.prevent="addRef(true)">+添加不缓存设置</a>
</div> </div>
<div class="margin"></div> <div class="margin"></div>
</div>` </div>`

View File

@@ -76,6 +76,8 @@ Vue.component("http-firewall-actions-box", {
blockScope: "global", blockScope: "global",
captchaLife: "", captchaLife: "",
captchaMaxFails: "",
captchaFailBlockTimeout: "",
get302Life: "", get302Life: "",
post307Life: "", post307Life: "",
recordIPType: "black", recordIPType: "black",
@@ -124,6 +126,22 @@ Vue.component("http-firewall-actions-box", {
this.actionOptions["life"] = v this.actionOptions["life"] = v
} }
}, },
captchaMaxFails: function (v) {
v = parseInt(v)
if (isNaN(v)) {
this.actionOptions["maxFails"] = 0
} else {
this.actionOptions["maxFails"] = v
}
},
captchaFailBlockTimeout: function (v) {
v = parseInt(v)
if (isNaN(v)) {
this.actionOptions["failBlockTimeout"] = 0
} else {
this.actionOptions["failBlockTimeout"] = v
}
},
get302Life: function (v) { get302Life: function (v) {
v = parseInt(v) v = parseInt(v)
if (isNaN(v)) { if (isNaN(v)) {
@@ -190,6 +208,8 @@ Vue.component("http-firewall-actions-box", {
this.blockTimeout = "" this.blockTimeout = ""
this.blockScope = "global" this.blockScope = "global"
this.captchaLife = "" this.captchaLife = ""
this.captchaMaxFails = ""
this.captchaFailBlockTimeout = ""
this.get302Life = "" this.get302Life = ""
this.post307Life = "" this.post307Life = ""
@@ -258,6 +278,14 @@ Vue.component("http-firewall-actions-box", {
if (config.options.life != null || config.options.life > 0) { if (config.options.life != null || config.options.life > 0) {
this.captchaLife = config.options.life.toString() this.captchaLife = config.options.life.toString()
} }
this.captchaMaxFails = ""
if (config.options.maxFails != null || config.options.maxFails > 0) {
this.captchaMaxFails = config.options.maxFails.toString()
}
this.captchaFailBlockTimeout = ""
if (config.options.failBlockTimeout != null || config.options.failBlockTimeout > 0) {
this.captchaFailBlockTimeout = config.options.failBlockTimeout.toString()
}
break break
case "notify": case "notify":
break break
@@ -559,7 +587,7 @@ Vue.component("http-firewall-actions-box", {
<td>封锁时间</td> <td>封锁时间</td>
<td> <td>
<div class="ui input right labeled"> <div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="10" v-model="blockTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/> <input type="text" style="width: 5em" maxlength="9" v-model="blockTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span> <span class="ui label">秒</span>
</div> </div>
</td> </td>
@@ -579,19 +607,39 @@ Vue.component("http-firewall-actions-box", {
<td>有效时间</td> <td>有效时间</td>
<td> <td>
<div class="ui input right labeled"> <div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="10" v-model="captchaLife" @keyup.enter="confirm()" @keypress.enter.prevent="1"/> <input type="text" style="width: 5em" maxlength="9" v-model="captchaLife" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span> <span class="ui label">秒</span>
</div> </div>
<p class="comment">验证通过后在这个时间内不再验证默认600秒。</p> <p class="comment">验证通过后在这个时间内不再验证默认600秒。</p>
</td> </td>
</tr> </tr>
<tr v-if="actionCode == 'captcha'">
<td>最多失败次数</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="9" v-model="captchaMaxFails" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">次</span>
</div>
<p class="comment">如果为空或者为0表示不限制。</p>
</td>
</tr>
<tr v-if="actionCode == 'captcha'">
<td>失败拦截时间</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="9" v-model="captchaFailBlockTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span>
</div>
<p class="comment">在达到最多失败次数大于0自动拦截的时间如果为0表示不自动拦截。</p>
</td>
</tr>
<!-- get_302 --> <!-- get_302 -->
<tr v-if="actionCode == 'get_302'"> <tr v-if="actionCode == 'get_302'">
<td>有效时间</td> <td>有效时间</td>
<td> <td>
<div class="ui input right labeled"> <div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="10" v-model="get302Life" @keyup.enter="confirm()" @keypress.enter.prevent="1"/> <input type="text" style="width: 5em" maxlength="9" v-model="get302Life" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span> <span class="ui label">秒</span>
</div> </div>
<p class="comment">验证通过后在这个时间内不再验证。</p> <p class="comment">验证通过后在这个时间内不再验证。</p>
@@ -603,7 +651,7 @@ Vue.component("http-firewall-actions-box", {
<td>有效时间</td> <td>有效时间</td>
<td> <td>
<div class="ui input right labeled"> <div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="10" v-model="post307Life" @keyup.enter="confirm()" @keypress.enter.prevent="1"/> <input type="text" style="width: 5em" maxlength="9" v-model="post307Life" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span> <span class="ui label">秒</span>
</div> </div>
<p class="comment">验证通过后在这个时间内不再验证。</p> <p class="comment">验证通过后在这个时间内不再验证。</p>
@@ -640,7 +688,7 @@ Vue.component("http-firewall-actions-box", {
<td>超时时间</td> <td>超时时间</td>
<td> <td>
<div class="ui input right labeled"> <div class="ui input right labeled">
<input type="text" style="width: 6em" maxlength="10" v-model="recordIPTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/> <input type="text" style="width: 6em" maxlength="9" v-model="recordIPTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span> <span class="ui label">秒</span>
</div> </div>
<p class="comment">0表示不超时。</p> <p class="comment">0表示不超时。</p>

View File

@@ -4,7 +4,8 @@ Vue.component("http-firewall-block-options", {
return { return {
blockOptions: this.vBlockOptions, blockOptions: this.vBlockOptions,
statusCode: this.vBlockOptions.statusCode, statusCode: this.vBlockOptions.statusCode,
timeout: this.vBlockOptions.timeout timeout: this.vBlockOptions.timeout,
isEditing: false
} }
}, },
watch: { watch: {
@@ -25,9 +26,15 @@ Vue.component("http-firewall-block-options", {
} }
} }
}, },
methods: {
edit: function () {
this.isEditing = !this.isEditing
}
},
template: `<div> template: `<div>
<input type="hidden" name="blockOptionsJSON" :value="JSON.stringify(blockOptions)"/> <input type="hidden" name="blockOptionsJSON" :value="JSON.stringify(blockOptions)"/>
<table class="ui table"> <a href="" @click.prevent="edit">状态码:{{statusCode}} / 提示内容:<span v-if="blockOptions.body != null && blockOptions.body.length > 0">[{{blockOptions.body.length}}字符]</span><span v-else class="disabled">[无]</span> / 超时时间:{{timeout}}秒 <i class="icon angle" :class="{up: isEditing, down: !isEditing}"></i></a>
<table class="ui table" v-show="isEditing">
<tr> <tr>
<td class="title">状态码</td> <td class="title">状态码</td>
<td> <td>

View File

@@ -14,7 +14,7 @@ Vue.component("http-firewall-rule-label", {
}, },
template: `<div> template: `<div>
<div class="ui label tiny basic"> <div class="ui label tiny basic" style="line-height: 1.5">
{{rule.name}}[{{rule.param}}] {{rule.name}}[{{rule.param}}]
<!-- cc2 --> <!-- cc2 -->
@@ -33,6 +33,9 @@ Vue.component("http-firewall-rule-label", {
{{rule.value}} {{rule.value}}
</span> </span>
<!-- description -->
<span v-if="rule.description != null && rule.description.length > 0" class="grey small">{{rule.description}}</span>
<a href="" v-if="rule.err != null && rule.err.length > 0" @click.prevent="showErr(rule.err)" style="color: #db2828; opacity: 1; border-bottom: 1px #db2828 dashed; margin-left: 0.5em">规则错误</a> <a href="" v-if="rule.err != null && rule.err.length > 0" @click.prevent="showErr(rule.err)" style="color: #db2828; opacity: 1; border-bottom: 1px #db2828 dashed; margin-left: 0.5em">规则错误</a>
</div> </div>
</div>` </div>`

View File

@@ -55,6 +55,9 @@ Vue.component("http-firewall-rules-box", {
<span v-if="rule.paramFilters != null && rule.paramFilters.length > 0" v-for="paramFilter in rule.paramFilters"> | {{paramFilter.code}}</span> <var>{{rule.operator}}</var> {{rule.value}} <span v-if="rule.paramFilters != null && rule.paramFilters.length > 0" v-for="paramFilter in rule.paramFilters"> | {{paramFilter.code}}</span> <var>{{rule.operator}}</var> {{rule.value}}
</span> </span>
<!-- description -->
<span v-if="rule.description != null && rule.description.length > 0" class="grey small">{{rule.description}}</span>
<a href="" title="修改" @click.prevent="updateRule(index, rule)"><i class="icon pencil small"></i></a> <a href="" title="修改" @click.prevent="updateRule(index, rule)"><i class="icon pencil small"></i></a>
<a href="" title="删除" @click.prevent="removeRule(index)"><i class="icon remove"></i></a> <a href="" title="删除" @click.prevent="removeRule(index)"><i class="icon remove"></i></a>
</div> </div>

View File

@@ -81,7 +81,7 @@ Vue.component("http-pages-and-shutdown-box", {
<h1>网站升级中</h1> <h1>网站升级中</h1>
<p>为了给您提供更好的服务,我们正在升级网站,请稍后重新访问。</p> <p>为了给您提供更好的服务,我们正在升级网站,请稍后重新访问。</p>
<address>Request ID: \${requestId}, Powered by GoEdge.</address> <address>Request ID: \${requestId}.</address>
</body> </body>
</html>` </html>`

View File

@@ -0,0 +1,37 @@
Vue.component("http-request-scripts-config-box", {
props: ["vRequestScriptsConfig"],
data: function () {
let config = this.vRequestScriptsConfig
if (config == null) {
config = {}
}
return {
config: config
}
},
methods: {
changeInitGroup: function (group) {
this.config.initGroup = group
this.$forceUpdate()
},
changeRequestGroup: function (group) {
this.config.requestGroup = group
this.$forceUpdate()
}
},
template: `<div>
<input type="hidden" name="requestScriptsJSON" :value="JSON.stringify(config)"/>
<div class="margin"></div>
<h4 style="margin-bottom: 0">请求初始化</h4>
<p class="comment">在请求刚初始化时调用此时自定义Header等尚未生效。</p>
<div>
<script-group-config-box :v-group="config.initGroup" @change="changeInitGroup"></script-group-config-box>
</div>
<h4 style="margin-bottom: 0">准备发送请求</h4>
<p class="comment">在准备执行请求或者转发请求之前调用此时自定义Header、源站等已准备好。</p>
<div>
<script-group-config-box :v-group="config.requestGroup" @change="changeRequestGroup"></script-group-config-box>
</div>
<div class="margin"></div>
</div>`
})

View File

@@ -92,6 +92,9 @@ Vue.component("origin-list-table", {
<div v-if="origin.domains != null && origin.domains.length > 0"> <div v-if="origin.domains != null && origin.domains.length > 0">
<grey-label v-for="domain in origin.domains">{{domain}}</grey-label> <grey-label v-for="domain in origin.domains">{{domain}}</grey-label>
</div> </div>
<div v-if="origin.hasCert">
<tiny-basic-label>证书</tiny-basic-label>
</div>
</td> </td>
<td :class="{disabled:!origin.isOn}">{{origin.weight}}</td> <td :class="{disabled:!origin.isOn}">{{origin.weight}}</td>
<td> <td>

View File

@@ -0,0 +1,53 @@
Vue.component("script-config-box", {
props: ["id", "v-script-config", "comment"],
data: function () {
let config = this.vScriptConfig
if (config == null) {
config = {
isPrior: false,
isOn: false,
code: ""
}
}
if (config.code.length == 0) {
config.code = "\n\n\n\n"
}
return {
config: config
}
},
watch: {
"config.isOn": function () {
this.change()
}
},
methods: {
change: function () {
this.$emit("change", this.config)
},
changeCode: function (code) {
this.config.code = code
this.change()
}
},
template: `<div>
<table class="ui table definition selectable">
<tbody>
<tr>
<td class="title">是否启用</td>
<td><checkbox v-model="config.isOn"></checkbox></td>
</tr>
</tbody>
<tbody>
<tr :style="{opacity: !config.isOn ? 0.5 : 1}">
<td>脚本代码</td>
<td><source-code-box :id="id" type="text/javascript" :read-only="false" @change="changeCode">{{config.code}}</source-code-box>
<p class="comment">{{comment}}</p>
</td>
</tr>
</tbody>
</table>
</div>`
})

View File

@@ -0,0 +1,38 @@
Vue.component("script-group-config-box", {
props: ["v-group"],
data: function () {
let group = this.vGroup
if (group == null) {
group = {
isPrior: false,
isOn: true,
scripts: []
}
}
if (group.scripts == null) {
group.scripts = []
}
let script = null
if (group.scripts.length > 0) {
script = group.scripts[group.scripts.length - 1]
}
return {
group: group,
script: script
}
},
methods: {
changeScript: function (script) {
this.group.scripts = [script] // 目前只支持单个脚本
this.change()
},
change: function () {
this.$emit("change", this.group)
}
},
template: `<div>
<script-config-box :v-script-config="script" comment="在接收到客户端请求之后立即调用。预置req、resp变量。" @change="changeScript"></script-config-box>
</div>`
})

View File

@@ -1,18 +1,29 @@
Vue.component("ssl-certs-box", { Vue.component("ssl-certs-box", {
props: [ props: [
"v-certs", // 证书列表 "v-certs", // 证书列表
"v-cert", // 单个证书
"v-protocol", // 协议https|tls "v-protocol", // 协议https|tls
"v-view-size", // 弹窗尺寸 "v-view-size", // 弹窗尺寸normal, mini
"v-single-mode" // 单证书模式 "v-single-mode", // 单证书模式
"v-description" // 描述文字
], ],
data: function () { data: function () {
let certs = this.vCerts let certs = this.vCerts
if (certs == null) { if (certs == null) {
certs = [] certs = []
} }
if (this.vCert != null) {
certs.push(this.vCert)
}
let description = this.vDescription
if (description == null || typeof (description) != "string") {
description = ""
}
return { return {
certs: certs certs: certs,
description: description
} }
}, },
methods: { methods: {
@@ -77,13 +88,14 @@ Vue.component("ssl-certs-box", {
template: `<div> template: `<div>
<input type="hidden" name="certIdsJSON" :value="JSON.stringify(certIds())"/> <input type="hidden" name="certIdsJSON" :value="JSON.stringify(certIds())"/>
<div v-if="certs != null && certs.length > 0"> <div v-if="certs != null && certs.length > 0">
<div class="ui label small" v-for="(cert, index) in certs"> <div class="ui label small basic" v-for="(cert, index) in certs">
{{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}} &nbsp; <a href="" title="删除" @click.prevent="removeCert(index)"><i class="icon remove"></i></a> {{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}} &nbsp; <a href="" title="删除" @click.prevent="removeCert(index)"><i class="icon remove"></i></a>
</div> </div>
<div class="ui divider" v-if="buttonsVisible()"></div> <div class="ui divider" v-if="buttonsVisible()"></div>
</div> </div>
<div v-else> <div v-else>
<span class="red">选择或上传证书后<span v-if="vProtocol == 'https'">HTTPS</span><span v-if="vProtocol == 'tls'">TLS</span>服务才能生效。</span> <span class="red" v-if="description.length == 0">选择或上传证书后<span v-if="vProtocol == 'https'">HTTPS</span><span v-if="vProtocol == 'tls'">TLS</span>服务才能生效。</span>
<span class="grey" v-if="description.length > 0">{{description}}</span>
<div class="ui divider" v-if="buttonsVisible()"></div> <div class="ui divider" v-if="buttonsVisible()"></div>
</div> </div>
<div v-if="buttonsVisible()"> <div v-if="buttonsVisible()">

View File

@@ -73,6 +73,12 @@
padding-bottom: 2em; padding-bottom: 2em;
overflow-y: auto; overflow-y: auto;
} }
@media screen and (max-width: 512px) {
.right-box {
left: 13em;
padding-right: 1em;
}
}
body.expanded .right-box { body.expanded .right-box {
left: 10em; left: 10em;
} }

File diff suppressed because one or more lines are too long

View File

@@ -98,6 +98,13 @@
overflow-y: auto; overflow-y: auto;
} }
@media screen and (max-width: 512px) {
.right-box {
left: 13em;
padding-right: 1em;
}
}
body.expanded .right-box { body.expanded .right-box {
left: 10em; left: 10em;
} }

View File

@@ -21,13 +21,19 @@
<option value="success">成功</option> <option value="success">成功</option>
</select> </select>
</div> </div>
<div class="ui field">
<select class="ui dropdown" name="tag" v-model="tag">
<option value="">[标签]</option>
<option v-for="tag in tags" :value="tag.code">{{tag.name}}</option>
</select>
</div>
<div class="ui field"> <div class="ui field">
<input type="text" name="keyword" style="width:10em" v-model="keyword" placeholder="关键词"/> <input type="text" name="keyword" style="width:10em" v-model="keyword" placeholder="关键词"/>
</div> </div>
<div class="ui field"> <div class="ui field">
<button type="submit" class="ui button">查询</button> <button type="submit" class="ui button">查询</button>
</div> </div>
<div class="ui field" v-if="dayFrom.length > 0 || dayTo.length > 0 || keyword.length > 0 || level.length > 0"> <div class="ui field" v-if="dayFrom.length > 0 || dayTo.length > 0 || keyword.length > 0 || level.length > 0 || tag.length > 0">
<a :href="'/clusters/cluster/node/logs?clusterId=' + clusterId + '&nodeId=' + nodeId">[清除条件]</a> <a :href="'/clusters/cluster/node/logs?clusterId=' + clusterId + '&nodeId=' + nodeId">[清除条件]</a>
</div> </div>
</div> </div>

View File

@@ -10,8 +10,8 @@
<tr> <tr>
<td class="title">CPU线程数</td> <td class="title">CPU线程数</td>
<td> <td>
<input type="text" name="maxCPU" v-model="node.maxCPU" style="width:5em" maxlength="5em"/> <input type="text" name="maxCPU" v-model="node.maxCPU" style="width:5em" maxlength="4"/>
<p class="comment">当前节点可以使用的最多的CPU线程数如果为0表示可以使用全部CPU</p> <p class="comment">当前节点可以使用的最多的CPU线程数如果为0则默认为4倍的CPU线程数</p>
</td> </td>
</tr> </tr>
</table> </table>

View File

@@ -59,6 +59,13 @@
<p class="comment">如果为0则默认为{{defaultNodeTCPMaxConnections}}。</p> <p class="comment">如果为0则默认为{{defaultNodeTCPMaxConnections}}。</p>
</td> </td>
</tr> </tr>
<tr>
<td>自动开放端口</td>
<td>
<checkbox name="autoOpenPorts" v-model="cluster.autoOpenPorts"></checkbox>
<p class="comment">选中后会自动尝试在边缘节点正在运行的firewalld中开放所需端口如果有别的防火墙或者安全策略仍然需要手工操作。</p>
</td>
</tr>
</tbody> </tbody>
</table> </table>
<submit-btn></submit-btn> <submit-btn></submit-btn>

View File

@@ -6,10 +6,7 @@
<form class="ui form" method="get" action="/clusters/ip-addrs"> <form class="ui form" method="get" action="/clusters/ip-addrs">
<div class="ui fields inline"> <div class="ui fields inline">
<div class="ui field"> <div class="ui field">
集群: <node-cluster-combo-box :v-cluster-id="clusterId"></node-cluster-combo-box>
</div>
<div class="ui field">
<cluster-selector :v-cluster-id="clusterId"></cluster-selector>
</div> </div>
<div class="ui field"> <div class="ui field">
状态: 状态:

View File

@@ -22,13 +22,27 @@
<option value="success">成功</option> <option value="success">成功</option>
</select> </select>
</div> </div>
<div class="ui field">
<select class="ui dropdown" name="tag" v-model="tag">
<option value="">[标签]</option>
<option v-for="tag in tags" :value="tag.code">{{tag.name}}</option>
</select>
</div>
<div class="ui field"> <div class="ui field">
<input type="text" name="keyword" style="width:10em" v-model="keyword" placeholder="关键词"/> <input type="text" name="keyword" style="width:10em" v-model="keyword" placeholder="关键词"/>
</div> </div>
</div>
<div class="ui fields inline" style="margin-top: 0.5em">
<div class="ui field">
<node-cluster-combo-box :v-cluster-id="clusterId" @change="changeCluster"></node-cluster-combo-box>
</div>
<div class="ui field" v-if="clusterId > 0">
<node-combo-box :v-cluster-id="clusterId" :v-node-id="nodeId"></node-combo-box>
</div>
<div class="ui field"> <div class="ui field">
<button type="submit" class="ui button">查询</button> <button type="submit" class="ui button">查询</button>
</div> </div>
<div class="ui field" v-if="dayFrom.length > 0 || dayTo.length > 0 || keyword.length > 0 || level.length > 0"> <div class="ui field" v-if="dayFrom.length > 0 || dayTo.length > 0 || keyword.length > 0 || level.length > 0 || tag.length > 0 || clusterId > 0 || nodeId > 0">
<a :href="'/clusters/logs?type=' + type">[清除条件]</a> <a :href="'/clusters/logs?type=' + type">[清除条件]</a>
</div> </div>
</div> </div>

View File

@@ -38,4 +38,8 @@ Tea.context(function () {
}) })
}) })
} }
this.changeCluster = function (clusterId) {
this.clusterId = clusterId
}
}) })

View File

@@ -31,13 +31,13 @@
</div> </div>
<div class="ui field"> <div class="ui field">
<div class="ui left icon input"> <div class="ui left icon input">
<i class="ui user icon"></i> <i class="ui user icon small"></i>
<input type="text" name="username" v-model="username" placeholder="请输入用户名" maxlength="200" ref="usernameRef" @input="changeUsername"/> <input type="text" name="username" v-model="username" placeholder="请输入用户名" maxlength="200" ref="usernameRef" @input="changeUsername"/>
</div> </div>
</div> </div>
<div class="ui field"> <div class="ui field">
<div class="ui left icon input"> <div class="ui left icon input">
<i class="ui lock icon"></i> <i class="ui lock icon small"></i>
<input type="password" v-model="password" placeholder="请输入密码" maxlength="200" @input="changePassword()" ref="passwordRef"/> <input type="password" v-model="password" placeholder="请输入密码" maxlength="200" @input="changePassword()" ref="passwordRef"/>
</div> </div>
</div> </div>

View File

@@ -26,7 +26,7 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="color-border">文件目录最大容量</td> <td class="color-border">文件目录最大容量 *</td>
<td> <td>
<size-capacity-box :v-name="'capacityJSON'" :v-count="128" :v-unit="'gb'" :key="'capacityJSON1'"></size-capacity-box> <size-capacity-box :v-name="'capacityJSON'" :v-count="128" :v-unit="'gb'" :key="'capacityJSON1'"></size-capacity-box>
<p class="comment">作为二级缓存的文件目录允许缓存的最大容量如果为0表示没有限制。</p> <p class="comment">作为二级缓存的文件目录允许缓存的最大容量如果为0表示没有限制。</p>
@@ -38,6 +38,13 @@
<size-capacity-box :v-name="'fileMemoryCapacityJSON'" :v-count="0" :v-unit="'gb'" :key="'fileMemoryCapacityJSON'"></size-capacity-box> <size-capacity-box :v-name="'fileMemoryCapacityJSON'" :v-count="0" :v-unit="'gb'" :key="'fileMemoryCapacityJSON'"></size-capacity-box>
<p class="comment">作为一级缓存的内存最大容量如果为0表示不使用内存作为一级缓存。</p> <p class="comment">作为一级缓存的内存最大容量如果为0表示不使用内存作为一级缓存。</p>
</td> </td>
</tr>
<tr>
<td class="color-border">缓存文件句柄缓存</td>
<td>
<input type="text" name="fileOpenFileCacheMax" maxlength="6" value="0" style="width: 10em"/>
<p class="comment">保持缓存文件句柄提升缓存文件打开速度建议数量是缓存文件数量的10%。</p>
</td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -10,13 +10,16 @@
<div class="margin"></div> <div class="margin"></div>
<form class="ui form" method="get" action="/servers/components/cache"> <form class="ui form" method="get" action="/servers/components/cache">
<div class="ui fields inline"> <div class="ui fields inline">
<div class="ui field">
<node-cluster-combo-box :v-cluster-id="clusterId"></node-cluster-combo-box>
</div>
<div class="ui field"> <div class="ui field">
<input type="text" name="keyword" v-model="keyword" placeholder="策略名称..."/> <input type="text" name="keyword" v-model="keyword" placeholder="策略名称..."/>
</div> </div>
<div class="ui field"> <div class="ui field">
<button type="submit" class="ui button">搜索</button> <button type="submit" class="ui button">搜索</button>
&nbsp; &nbsp;
<a :href="Tea.url('.')" v-if="keyword.length > 0">[清除条件]</a> <a :href="Tea.url('.')" v-if="keyword.length > 0 || clusterId > 0">[清除条件]</a>
</div> </div>
</div> </div>
</form> </form>

View File

@@ -40,6 +40,10 @@
<p class="comment">作为一级缓存的内存最大容量如果为0表示不使用内存作为一级缓存。</p> <p class="comment">作为一级缓存的内存最大容量如果为0表示不使用内存作为一级缓存。</p>
</td> </td>
</tr> </tr>
<tr v-if="cachePolicy.options.openFileCache != null && cachePolicy.options.openFileCache.isOn && cachePolicy.options.openFileCache.max > 0">
<td>缓存文件句柄缓存</td>
<td>{{cachePolicy.options.openFileCache.max}}</td>
</tr>
</tbody> </tbody>
<tbody v-if="cachePolicy.type != 'file'"> <tbody v-if="cachePolicy.type != 'file'">

View File

@@ -47,6 +47,13 @@
<size-capacity-box :v-name="'fileMemoryCapacityJSON'" :v-count="0" :v-unit="'gb'"></size-capacity-box> <size-capacity-box :v-name="'fileMemoryCapacityJSON'" :v-count="0" :v-unit="'gb'"></size-capacity-box>
<p class="comment">作为一级缓存的内存最大容量如果为0表示不使用内存作为一级缓存。</p> <p class="comment">作为一级缓存的内存最大容量如果为0表示不使用内存作为一级缓存。</p>
</td> </td>
</tr>
<tr>
<td class="color-border">缓存文件句柄缓存</td>
<td>
<input type="text" name="fileOpenFileCacheMax" v-model="fileOpenFileCacheMax" maxlength="6" value="0" style="width: 10em"/>
<p class="comment">保持缓存文件句柄提升缓存文件打开速度建议数量是缓存文件数量的10%。</p>
</td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -2,4 +2,9 @@ Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功 ") this.success = NotifyReloadSuccess("保存成功 ")
this.policyType = this.cachePolicy.type this.policyType = this.cachePolicy.type
this.fileOpenFileCacheMax = 0
if (this.cachePolicy.type == "file" && this.cachePolicy.options.openFileCache != null && this.cachePolicy.options.openFileCache.isOn && this.cachePolicy.options.openFileCache.max > 0) {
this.fileOpenFileCacheMax = this.cachePolicy.options.openFileCache.max
}
}) })

View File

@@ -140,6 +140,10 @@
<td> <td>
<http-firewall-param-filters-box :v-filters="rule.paramFilters"></http-firewall-param-filters-box> <http-firewall-param-filters-box :v-filters="rule.paramFilters"></http-firewall-param-filters-box>
</td> </td>
</tr>
<tr>
<td>备注</td>
<td><input type="text" name="description" v-model="rule.description"/></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -45,8 +45,8 @@
</thead> </thead>
<tbody v-for="set in sets" :data-set-id="set.id"> <tbody v-for="set in sets" :data-set-id="set.id">
<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 nowrap=""><a href="" @click.prevent="updateSet(set.id)"><span :class="{disabled:!set.isOn}">{{set.name}}</span></a> <td nowrap=""><a :name="'set' + set.id"></a><a href="" @click.prevent="updateSet(set.id)"><span :class="{disabled:!set.isOn}">{{set.name}}</span> <i class="icon clone outline small"></i> </a>
<p style="margin-top:0.5em"> <p style="margin-top:0.5em">
<label-on :v-is-on="set.isOn"></label-on> <label-on :v-is-on="set.isOn"></label-on>
</p> </p>

View File

@@ -10,13 +10,16 @@
<div class="margin"></div> <div class="margin"></div>
<form class="ui form" method="get" action="/servers/components/waf"> <form class="ui form" method="get" action="/servers/components/waf">
<div class="ui fields inline"> <div class="ui fields inline">
<div class="ui field">
<node-cluster-combo-box :v-cluster-id="clusterId"></node-cluster-combo-box>
</div>
<div class="ui field"> <div class="ui field">
<input type="text" name="keyword" v-model="keyword" placeholder="策略名称..."/> <input type="text" name="keyword" v-model="keyword" placeholder="策略名称..."/>
</div> </div>
<div class="ui field"> <div class="ui field">
<button type="submit" class="ui button">搜索</button> <button type="submit" class="ui button">搜索</button>
&nbsp; &nbsp;
<a :href="Tea.url('.')" v-if="keyword.length > 0">[清除条件]</a> <a :href="Tea.url('.')" v-if="keyword.length > 0 || clusterId > 0">[清除条件]</a>
</div> </div>
</div> </div>
</form> </form>

View File

@@ -51,6 +51,47 @@
</div> </div>
</td> </td>
</tr> </tr>
<tr>
<td>使用系统防火墙</td>
<td>
<span class="green" v-if="firewallPolicy.useLocalFirewall">启用</span>
<span class="disabled" v-if="!firewallPolicy.useLocalFirewall">不启用</span>
<p class="comment" v-if="firewallPolicy.useLocalFirewall">可以在合适的时候自动使用系统自带防火墙进行防御。</p>
</td>
</tr>
<tr>
<td>SYN Flood防御</td>
<td>
<span v-if="firewallPolicy.synFlood == null || !firewallPolicy.synFlood.isOn" class="disabled">未启用</span>
<table class="ui table selectable" v-if="firewallPolicy.synFlood != null && firewallPolicy.synFlood.isOn">
<tr>
<td class="title">是否启用</td>
<td>
<span class="green">启用</span>
</td>
</tr>
<tr>
<td>空连接次数</td>
<td>
{{firewallPolicy.synFlood.minAttempts}}次/分钟
</td>
</tr>
<tr>
<td>封禁时间</td>
<td>
{{firewallPolicy.synFlood.timeoutSeconds}}秒
</td>
</tr>
<tr>
<td>忽略局域网访问</td>
<td>
<span v-if="firewallPolicy.synFlood.ignoreLocal" class="green">Y</span>
<span class="disabled" v-else>N</span>
</td>
</tr>
</table>
</td>
</tr>
<tr> <tr>
<td>描述</td> <td>描述</td>
<td> <td>

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