Compare commits

..

50 Commits

Author SHA1 Message Date
刘祥超
4ed4f165ac URL跳转可以设置是否保留参数 2022-02-20 09:17:30 +08:00
刘祥超
eb8419fda0 Update components.js 2022-02-17 17:24:23 +08:00
刘祥超
b4a7ee0e89 域名支持--(连续的连字符) 2022-02-17 17:24:14 +08:00
刘祥超
83ad1bc529 优化界面 2022-02-15 14:54:45 +08:00
刘祥超
56948d3035 优化菜单中badge显示 2022-02-14 08:58:31 +08:00
刘祥超
e71e5ad57e 支持默认价格设置 2022-01-23 20:16:02 +08:00
刘祥超
e06d1fa5b0 实现带宽计费套餐 2022-01-23 19:16:22 +08:00
刘祥超
460439f6bd 修复服务访问日志不能使用集群、节点筛选的Bug 2022-01-20 16:28:43 +08:00
刘祥超
4b4072a47e 优化界面 2022-01-20 15:53:46 +08:00
刘祥超
65d19d92e9 增加API方法调用耗时统计 2022-01-19 16:53:38 +08:00
刘祥超
79e52d1c6e 优化demo模式进入命令 2022-01-19 15:58:54 +08:00
刘祥超
59963ee7b9 修复检查更新配置不起作用的Bug 2022-01-18 19:29:42 +08:00
刘祥超
88273b1c9b 修改版本为v0.4.1 2022-01-17 10:53:28 +08:00
刘祥超
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
157 changed files with 2813 additions and 338 deletions

View File

@@ -9,6 +9,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/nodes"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/gosock/pkg/gosock"
)
@@ -64,12 +65,17 @@ func main() {
fmt.Println("[ERROR]the service not started yet, you should start the service first")
return
}
_, err := sock.Send(&gosock.Command{Code: "demo"})
reply, err := sock.Send(&gosock.Command{Code: "demo"})
if err != nil {
fmt.Println("[ERROR]change demo mode failed: " + err.Error())
return
}
fmt.Println("change demo mode successfully")
var isDemo = maps.NewMap(reply.Params).GetBool("isDemo")
if isDemo {
fmt.Println("change demo mode to: on")
} else {
fmt.Println("change demo mode to: off")
}
})
app.On("generate", func() {
err := gen.Generate()

View File

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

View File

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

View File

@@ -325,7 +325,9 @@ func (this *AdminNode) listenSock() error {
_ = cmd.ReplyOk()
case "demo":
teaconst.IsDemoMode = !teaconst.IsDemoMode
_ = cmd.ReplyOk()
_ = cmd.Reply(&gosock.Command{
Params: map[string]interface{}{"isDemo": teaconst.IsDemoMode},
})
case "info":
exePath, _ := os.Executable()
_ = cmd.Reply(&gosock.Command{

View File

@@ -161,6 +161,10 @@ func (this *RPCClient) APINodeRPC() pb.APINodeServiceClient {
return pb.NewAPINodeServiceClient(this.pickConn())
}
func (this *RPCClient) APIMethodStatRPC() pb.APIMethodStatServiceClient {
return pb.NewAPIMethodStatServiceClient(this.pickConn())
}
func (this *RPCClient) UserNodeRPC() pb.UserNodeServiceClient {
return pb.NewUserNodeServiceClient(this.pickConn())
}
@@ -382,6 +386,10 @@ func (this *RPCClient) UserBillRPC() pb.UserBillServiceClient {
return pb.NewUserBillServiceClient(this.pickConn())
}
func (this *RPCClient) ServerBillRPC() pb.ServerBillServiceClient {
return pb.NewServerBillServiceClient(this.pickConn())
}
func (this *RPCClient) UserAccountRPC() pb.UserAccountServiceClient {
return pb.NewUserAccountServiceClient(this.pickConn())
}

View File

@@ -8,6 +8,9 @@ import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/events"
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
stringutil "github.com/iwind/TeaGo/utils/string"
@@ -46,6 +49,30 @@ func (this *CheckUpdatesTask) Start() {
}
func (this *CheckUpdatesTask) Loop() error {
// 检查是否开启
rpcClient, err := rpc.SharedRPC()
if err != nil {
return err
}
valueResp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeCheckUpdates})
if err != nil {
return err
}
var valueJSON = valueResp.ValueJSON
var config = &systemconfigs.CheckUpdatesConfig{AutoCheck: false}
if len(valueJSON) > 0 {
err = json.Unmarshal(valueJSON, config)
if err != nil {
return errors.New("decode config failed: " + err.Error())
}
if !config.AutoCheck {
return nil
}
} else {
return nil
}
// 开始检查
type Response struct {
Code int `json:"code"`
Message string `json:"message"`

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 (
"fmt"
"github.com/iwind/TeaGo/types"
"strconv"
)
@@ -40,3 +41,16 @@ func FormatBytes(bytes int64) string {
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(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

@@ -9,6 +9,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
@@ -26,11 +27,11 @@ func (this *IndexAction) RunGet(params struct{}) {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
var count = countResp.Count
var page = this.NewPage(count)
this.Data["page"] = page.AsHTML()
nodeMaps := []maps.Map{}
var nodeMaps = []maps.Map{}
if count > 0 {
nodesResp, err := this.RPC().APINodeRPC().ListEnabledAPINodes(this.AdminContext(), &pb.ListEnabledAPINodesRequest{
Offset: page.Offset,
@@ -106,5 +107,13 @@ func (this *IndexAction) RunGet(params struct{}) {
}
this.Data["nodes"] = nodeMaps
// 检查是否有调试数据
countMethodStatsResp, err := this.RPC().APIMethodStatRPC().CountAPIMethodStatsWithDay(this.AdminContext(), &pb.CountAPIMethodStatsWithDayRequest{Day: timeutil.Format("Ymd")})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["hasMethodStats"] = countMethodStatsResp.Count > 0
this.Show()
}

View File

@@ -16,6 +16,7 @@ func init() {
Helper(settingutils.NewAdvancedHelper("apiNodes")).
Prefix("/api").
Get("", new(IndexAction)).
Get("/methodStats", new(MethodStatsAction)).
GetPost("/node/createPopup", new(node.CreatePopupAction)).
Post("/delete", new(DeleteAction)).
EndAll()

View File

@@ -0,0 +1,78 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package api
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"sort"
"strings"
)
type MethodStatsAction struct {
actionutils.ParentAction
}
func (this *MethodStatsAction) Init() {
this.Nav("", "", "")
}
func (this *MethodStatsAction) RunGet(params struct {
Order string
Method string
Tag string
}) {
this.Data["order"] = params.Order
this.Data["method"] = params.Method
this.Data["tag"] = params.Tag
statsResp, err := this.RPC().APIMethodStatRPC().FindAPIMethodStatsWithDay(this.AdminContext(), &pb.FindAPIMethodStatsWithDayRequest{Day: timeutil.Format("Ymd")})
if err != nil {
this.ErrorPage(err)
return
}
var pbStats = statsResp.ApiMethodStats
switch params.Order {
case "method":
sort.Slice(pbStats, func(i, j int) bool {
return pbStats[i].Method < pbStats[j].Method
})
case "costMs.desc":
sort.Slice(pbStats, func(i, j int) bool {
return pbStats[i].CostMs > pbStats[j].CostMs
})
case "peekMs.desc":
sort.Slice(pbStats, func(i, j int) bool {
return pbStats[i].PeekMs > pbStats[j].PeekMs
})
case "calls.desc":
sort.Slice(pbStats, func(i, j int) bool {
return pbStats[i].CountCalls > pbStats[j].CountCalls
})
}
var statMaps = []maps.Map{}
for _, stat := range pbStats {
if len(params.Method) > 0 && !strings.Contains(strings.ToLower(stat.Method), strings.ToLower(params.Method)) {
continue
}
if len(params.Tag) > 0 && !strings.Contains(strings.ToLower(stat.Tag), strings.ToLower(params.Tag)) {
continue
}
statMaps = append(statMaps, maps.Map{
"id": stat.Id,
"method": stat.Method,
"tag": stat.Tag,
"costMs": stat.CostMs,
"peekMs": stat.PeekMs,
"countCalls": stat.CountCalls,
})
}
this.Data["stats"] = statMaps
this.Show()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -17,7 +17,7 @@ func ValidateDomainFormat(domain string) bool {
if piece == "-" ||
strings.HasPrefix(piece, "-") ||
strings.HasSuffix(piece, "-") ||
strings.Contains(piece, "--") ||
//strings.Contains(piece, "--") ||
len(piece) > 63 ||
!regexp.MustCompile(`^[a-z0-9-]+$`).MatchString(piece) {
return false
@@ -75,7 +75,7 @@ func ValidateRecordName(name string) bool {
if piece == "-" ||
strings.HasPrefix(piece, "-") ||
strings.HasSuffix(piece, "-") ||
strings.Contains(piece, "--") ||
//strings.Contains(piece, "--") ||
len(piece) > 63 ||
!regexp.MustCompile(`^[_a-z0-9-]+$`).MatchString(piece) {
return false

View File

@@ -31,6 +31,7 @@ func (this *CreatePopupAction) RunPost(params struct {
// file
FileDir string
FileMemoryCapacityJSON []byte
FileOpenFileCacheMax int
CapacityJSON []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{
Dir: params.FileDir,
MemoryPolicy: &serverconfigs.HTTPCachePolicy{
Capacity: memoryCapacity,
},
OpenFileCache: openFileCacheConfig,
}
case serverconfigs.CachePolicyStorageMemory:
options = &serverconfigs.HTTPMemoryCacheStorage{
}
options = &serverconfigs.HTTPMemoryCacheStorage{}
default:
this.Fail("请选择正确的缓存类型")
}

View File

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

View File

@@ -55,6 +55,7 @@ func (this *UpdateAction) RunPost(params struct {
// file
FileDir string
FileMemoryCapacityJSON []byte
FileOpenFileCacheMax int
CapacityJSON []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{
Dir: params.FileDir,
MemoryPolicy: &serverconfigs.HTTPCachePolicy{
Capacity: memoryCapacity,
},
OpenFileCache: openFileCacheConfig,
}
case serverconfigs.CachePolicyStorageMemory:
options = &serverconfigs.HTTPMemoryCacheStorage{
}
options = &serverconfigs.HTTPMemoryCacheStorage{}
default:
this.Fail("请选择正确的缓存类型")
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type ListsAction struct {
@@ -109,6 +110,7 @@ func (this *ListsAction) RunGet(params struct {
"sourceGroup": sourceGroupMap,
"sourceSet": sourceSetMap,
"sourceServer": sourceServerMap,
"lifeSeconds": item.ExpiredAt - time.Now().Unix(),
})
}
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{
CountryId: int64(ChinaCountryId),
RegionCountryId: int64(ChinaCountryId),
})
if err != nil {
this.ErrorPage(err)
return
}
provinceMaps := []maps.Map{}
for _, province := range provincesResp.Provinces {
for _, province := range provincesResp.RegionProvinces {
provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id,
"name": province.Name,

View File

@@ -84,15 +84,18 @@ func (this *PolicyAction) RunGet(params struct {
if len(firewallPolicy.Mode) == 0 {
firewallPolicy.Mode = firewallconfigs.FirewallModeDefend
}
this.Data["firewallPolicy"] = maps.Map{
"id": firewallPolicy.Id,
"name": firewallPolicy.Name,
"isOn": firewallPolicy.IsOn,
"description": firewallPolicy.Description,
"mode": firewallPolicy.Mode,
"modeInfo": firewallconfigs.FindFirewallMode(firewallPolicy.Mode),
"groups": internalGroups,
"blockOptions": firewallPolicy.BlockOptions,
"id": firewallPolicy.Id,
"name": firewallPolicy.Name,
"isOn": firewallPolicy.IsOn,
"description": firewallPolicy.Description,
"mode": firewallPolicy.Mode,
"modeInfo": firewallconfigs.FindFirewallMode(firewallPolicy.Mode),
"groups": internalGroups,
"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()
// syn flood
if firewallPolicy.SYNFlood == nil {
firewallPolicy.SYNFlood = &firewallconfigs.SYNFloodConfig{
IsOn: false,
MinAttempts: 10,
TimeoutSeconds: 600,
IgnoreLocal: true,
}
}
this.Data["firewallPolicy"] = maps.Map{
"id": firewallPolicy.Id,
"name": firewallPolicy.Name,
"description": firewallPolicy.Description,
"isOn": firewallPolicy.IsOn,
"mode": firewallPolicy.Mode,
"blockOptions": firewallPolicy.BlockOptions,
"id": firewallPolicy.Id,
"name": firewallPolicy.Name,
"description": firewallPolicy.Description,
"isOn": firewallPolicy.IsOn,
"mode": firewallPolicy.Mode,
"blockOptions": firewallPolicy.BlockOptions,
"useLocalFirewall": firewallPolicy.UseLocalFirewall,
"synFloodConfig": firewallPolicy.SYNFlood,
}
// 预置分组
@@ -87,6 +99,8 @@ func (this *UpdateAction) RunPost(params struct {
Description string
IsOn bool
Mode string
UseLocalFirewall bool
SynFloodJSON []byte
Must *actions.Must
}) {
@@ -112,6 +126,8 @@ func (this *UpdateAction) RunPost(params struct {
FirewallGroupCodes: params.GroupCodes,
BlockOptionsJSON: params.BlockOptionsJSON,
Mode: params.Mode,
UseLocalFirewall: params.UseLocalFirewall,
SynFloodJSON: params.SynFloodJSON,
})
if err != nil {
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()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,14 +22,28 @@ func (this *IndexAction) Init() {
func (this *IndexAction) RunGet(params struct {
Ip string
GlobalOnly bool
Unread bool
}) {
this.Data["type"] = ""
this.Data["ip"] = params.Ip
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{
Ip: params.Ip,
GlobalOnly: params.GlobalOnly,
Unread: params.Unread,
})
if err != nil {
this.ErrorPage(err)
@@ -42,6 +56,7 @@ func (this *IndexAction) RunGet(params struct {
itemsResp, err := this.RPC().IPItemRPC().ListAllEnabledIPItems(this.AdminContext(), &pb.ListAllEnabledIPItemsRequest{
Ip: params.Ip,
GlobalOnly: params.GlobalOnly,
Unread: params.Unread,
Offset: page.Offset,
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{
"id": item.Id,
"ipFrom": item.IpFrom,
@@ -127,11 +152,14 @@ func (this *IndexAction) RunGet(params struct {
"expiredTime": expiredTime,
"reason": item.Reason,
"type": item.Type,
"isRead": item.IsRead,
"lifeSeconds": item.ExpiredAt - time.Now().Unix(),
"eventLevelName": firewallconfigs.FindFirewallEventLevelName(item.EventLevel),
"sourcePolicy": sourcePolicyMap,
"sourceGroup": sourceGroupMap,
"sourceSet": sourceSetMap,
"sourceServer": sourceServerMap,
"sourceNode": sourceNodeMap,
"list": listMap,
"policy": policyMap,
})

View File

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

View File

@@ -8,6 +8,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type ItemsAction struct {
@@ -112,6 +113,7 @@ func (this *ItemsAction) RunGet(params struct {
"sourceGroup": sourceGroupMap,
"sourceSet": sourceSetMap,
"sourceServer": sourceServerMap,
"lifeSeconds": item.ExpiredAt - time.Now().Unix(),
})
}
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 {
Day string
Keyword string
Ip string
Domain string
HasError int
HasWAF int
ClusterId int64
NodeId int64
Day string
Keyword string
Ip string
Domain string
HasError int
HasWAF int
RequestId string
ServerId int64
@@ -38,6 +40,8 @@ func (this *IndexAction) RunGet(params struct {
params.Day = timeutil.Format("Y-m-d")
}
this.Data["clusterId"] = params.ClusterId
this.Data["nodeId"] = params.NodeId
this.Data["serverId"] = 0
this.Data["path"] = this.Request.URL.Path
this.Data["day"] = params.Day
@@ -66,6 +70,8 @@ func (this *IndexAction) RunGet(params struct {
var before = time.Now()
resp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
RequestId: params.RequestId,
NodeClusterId: params.ClusterId,
NodeId: params.NodeId,
ServerId: params.ServerId,
HasError: params.HasError > 0,
HasFirewallPolicy: params.HasWAF > 0,
@@ -108,6 +114,8 @@ func (this *IndexAction) RunGet(params struct {
this.Data["hasPrev"] = true
prevResp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
RequestId: params.RequestId,
NodeClusterId: params.ClusterId,
NodeId: params.NodeId,
ServerId: params.ServerId,
HasError: params.HasError > 0,
HasFirewallPolicy: params.HasWAF > 0,

View File

@@ -29,6 +29,9 @@ func (this *HistoryAction) RunGet(params struct {
RequestId string
HasError int
ClusterId int64
NodeId int64
PageSize int
}) {
if len(params.Day) == 0 {
@@ -44,6 +47,8 @@ func (this *HistoryAction) RunGet(params struct {
this.Data["hasError"] = params.HasError
this.Data["hasWAF"] = params.HasWAF
this.Data["pageSize"] = params.PageSize
this.Data["clusterId"] = params.ClusterId
this.Data["nodeId"] = params.NodeId
day := params.Day
ipList := []string{}
@@ -66,6 +71,8 @@ func (this *HistoryAction) RunGet(params struct {
Keyword: params.Keyword,
Ip: params.Ip,
Domain: params.Domain,
NodeId: params.NodeId,
NodeClusterId: params.ClusterId,
Size: size,
})
if err != nil {
@@ -102,6 +109,8 @@ func (this *HistoryAction) RunGet(params struct {
Keyword: params.Keyword,
Ip: params.Ip,
Domain: params.Domain,
NodeId: params.NodeId,
NodeClusterId: params.ClusterId,
Size: size,
Reverse: true,
})

View File

@@ -22,6 +22,8 @@ func (this *IndexAction) RunGet(params struct {
RequestId string
Ip string
Domain string
ClusterId int64
NodeId int64
Keyword string
}) {
this.Data["serverId"] = params.ServerId
@@ -30,6 +32,8 @@ func (this *IndexAction) RunGet(params struct {
this.Data["domain"] = params.Domain
this.Data["keyword"] = params.Keyword
this.Data["path"] = this.Request.URL.Path
this.Data["clusterId"] = params.ClusterId
this.Data["nodeId"] = params.NodeId
// 记录最近使用
_, err := this.RPC().LatestItemRPC().IncreaseLatestItem(this.AdminContext(), &pb.IncreaseLatestItemRequest{
@@ -50,19 +54,23 @@ func (this *IndexAction) RunPost(params struct {
Keyword string
Ip string
Domain string
ClusterId int64
NodeId int64
Must *actions.Must
}) {
isReverse := len(params.RequestId) > 0
accessLogsResp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
ServerId: params.ServerId,
RequestId: params.RequestId,
Size: 20,
Day: timeutil.Format("Ymd"),
Keyword: params.Keyword,
Ip: params.Ip,
Domain: params.Domain,
Reverse: isReverse,
ServerId: params.ServerId,
RequestId: params.RequestId,
Size: 20,
Day: timeutil.Format("Ymd"),
Keyword: params.Keyword,
Ip: params.Ip,
Domain: params.Domain,
NodeId: params.NodeId,
NodeClusterId: params.ClusterId,
Reverse: isReverse,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -24,6 +24,8 @@ func (this *TodayAction) RunGet(params struct {
Keyword string
Ip string
Domain string
ClusterId int64
NodeId int64
PageSize int
}) {
@@ -40,6 +42,8 @@ func (this *TodayAction) RunGet(params struct {
this.Data["ip"] = params.Ip
this.Data["domain"] = params.Domain
this.Data["hasWAF"] = params.HasWAF
this.Data["clusterId"] = params.ClusterId
this.Data["nodeId"] = params.NodeId
resp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
RequestId: params.RequestId,
@@ -50,6 +54,8 @@ func (this *TodayAction) RunGet(params struct {
Keyword: params.Keyword,
Ip: params.Ip,
Domain: params.Domain,
NodeId: params.NodeId,
NodeClusterId: params.ClusterId,
Size: size,
})
if err != nil {
@@ -87,6 +93,8 @@ func (this *TodayAction) RunGet(params struct {
Keyword: params.Keyword,
Ip: params.Ip,
Domain: params.Domain,
NodeId: params.NodeId,
NodeClusterId: params.ClusterId,
Size: size,
Reverse: true,
})

View File

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

View File

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

View File

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

View File

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

View File

@@ -27,11 +27,13 @@ func (this *CreatePopupAction) RunGet(params struct {
}
func (this *CreatePopupAction) RunPost(params struct {
Mode string
BeforeURL string
AfterURL string
MatchPrefix bool
MatchRegexp bool
KeepRequestURI bool
KeepArgs bool
Status int
CondsJSON []byte
IsOn bool
@@ -99,12 +101,14 @@ func (this *CreatePopupAction) RunPost(params struct {
}
this.Data["redirect"] = maps.Map{
"mode": params.Mode,
"status": params.Status,
"beforeURL": params.BeforeURL,
"afterURL": params.AfterURL,
"matchPrefix": params.MatchPrefix,
"matchRegexp": params.MatchRegexp,
"keepRequestURI": params.KeepRequestURI,
"keepArgs": params.KeepArgs,
"conds": conds,
"isOn": params.IsOn,
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -113,6 +113,7 @@ func (this *DenyListAction) RunGet(params struct {
"ipTo": item.IpTo,
"createdTime": timeutil.FormatTime("Y-m-d", item.CreatedAt),
"expiredTime": expiredTime,
"lifeSeconds": item.ExpiredAt - time.Now().Unix(),
"reason": item.Reason,
"type": item.Type,
"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{
CountryId: int64(ChinaCountryId),
RegionCountryId: int64(ChinaCountryId),
})
if err != nil {
this.ErrorPage(err)
return
}
provinceMaps := []maps.Map{}
for _, province := range provincesResp.Provinces {
for _, province := range provincesResp.RegionProvinces {
provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id,
"name": province.Name,

View File

@@ -378,6 +378,16 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
"isActive": secondMenuItem == "traffic",
"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{

View File

@@ -31,12 +31,12 @@ func (this *IndexAction) RunGet(params struct{}) {
// 国家和地区
countryMaps := []maps.Map{}
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 {
this.ErrorPage(err)
return
}
country := countryResp.Country
country := countryResp.RegionCountry
if country != nil {
countryMaps = append(countryMaps, maps.Map{
"id": country.Id,
@@ -49,12 +49,12 @@ func (this *IndexAction) RunGet(params struct{}) {
// 省份
provinceMaps := []maps.Map{}
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 {
this.ErrorPage(err)
return
}
province := provinceResp.Province
province := provinceResp.RegionProvince
if province != nil {
provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id,

View File

@@ -29,7 +29,7 @@ func (this *SelectCountriesPopupAction) RunGet(params struct {
return
}
countryMaps := []maps.Map{}
for _, country := range countriesResp.Countries {
for _, country := range countriesResp.RegionCountries {
countryMaps = append(countryMaps, maps.Map{
"id": country.Id,
"name": country.Name,
@@ -50,12 +50,12 @@ func (this *SelectCountriesPopupAction) RunPost(params struct {
}) {
countryMaps := []maps.Map{}
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 {
this.ErrorPage(err)
return
}
country := countryResp.Country
country := countryResp.RegionCountry
if country == nil {
continue
}

View File

@@ -24,13 +24,13 @@ func (this *SelectProvincesPopupAction) RunGet(params struct {
}) {
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 {
this.ErrorPage(err)
return
}
provinceMaps := []maps.Map{}
for _, province := range provincesResp.Provinces {
for _, province := range provincesResp.RegionProvinces {
provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id,
"name": province.Name,
@@ -50,12 +50,12 @@ func (this *SelectProvincesPopupAction) RunPost(params struct {
}) {
provinceMaps := []maps.Map{}
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 {
this.ErrorPage(err)
return
}
province := provinceResp.Province
province := provinceResp.RegionProvince
if province == nil {
continue
}

View File

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

View File

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

View File

@@ -20,6 +20,11 @@ func (this *UserAction) RunGet(params struct {
}) {
err := userutils.InitUser(this.Parent(), params.UserId)
if err != nil {
if err == userutils.ErrUserNotFound {
this.RedirectURL("/users")
return
}
this.ErrorPage(err)
return
}
@@ -51,17 +56,35 @@ func (this *UserAction) RunGet(params struct {
}
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{
"id": user.Id,
"username": user.Username,
"fullname": user.Fullname,
"email": user.Email,
"tel": user.Tel,
"remark": user.Remark,
"mobile": user.Mobile,
"isOn": user.IsOn,
"cluster": clusterMap,
"countAccessKeys": countAccessKeys,
"id": user.Id,
"username": user.Username,
"fullname": user.Fullname,
"email": user.Email,
"tel": user.Tel,
"remark": user.Remark,
"mobile": user.Mobile,
"isOn": user.IsOn,
"cluster": clusterMap,
"countAccessKeys": countAccessKeys,
"isRejected": user.IsRejected,
"rejectReason": user.RejectReason,
"isVerified": user.IsVerified,
"registeredIP": user.RegisteredIP,
"registeredRegion": registeredRegion,
}
this.Show()

View File

@@ -7,6 +7,8 @@ import (
"github.com/iwind/TeaGo/maps"
)
var ErrUserNotFound = errors.New("not found user")
// InitUser 查找用户基本信息
func InitUser(p *actionutils.ParentAction, userId int64) error {
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
}
if resp.User == nil {
return errors.New("not found user")
return ErrUserNotFound
}
// 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"
)
func FindAllMenuMaps(nodeLogsType string, countUnreadNodeLogs int64) []maps.Map {
func FindAllMenuMaps(nodeLogsType string, countUnreadNodeLogs int64, countUnreadIPItems int64) []maps.Map {
return []maps.Map{
{
"code": "dashboard",
@@ -50,9 +50,10 @@ func FindAllMenuMaps(nodeLogsType string, countUnreadNodeLogs int64) []maps.Map
"code": "waf",
},
{
"name": "IP名单",
"url": "/servers/iplists",
"code": "iplist",
"name": "IP名单",
"url": "/servers/iplists",
"code": "iplist",
"badge": countUnreadIPItems,
},
{
"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 {
// 运行日志
var countUnreadNodeLogs int64 = 0
var nodeLogsType = ""
// IP名单
var countUnreadIPItems int64 = 0
// 父级动作
parentAction, ok := actionPtr.(actionutils.ActionInterface)
if ok {
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{
Role: nodeconfigs.NodeRoleNode,
IsUnread: true,
@@ -197,11 +202,18 @@ func (this *userMustAuth) modules(actionPtr actions.ActionWrapper, adminId int64
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{}
for _, m := range FindAllMenuMaps(nodeLogsType, countUnreadNodeLogs) {
for _, m := range FindAllMenuMaps(nodeLogsType, countUnreadNodeLogs, countUnreadIPItems) {
if m.GetString("code") == "finance" && !configloaders.ShowFinance() {
continue
}

File diff suppressed because it is too large Load Diff

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

View File

@@ -70,6 +70,18 @@ Vue.component("ip-list-table", {
.success(function () {
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>
@@ -103,7 +115,7 @@ Vue.component("ip-list-table", {
</td>
<td>
<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-else class="disabled">*</span>
<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">
<span class="ui label tiny basic red">已过期</span>
</div>
<div v-if="item.lifeSeconds != null && item.lifeSeconds > 0">
<span class="small grey">{{formatSeconds(item.lifeSeconds)}}</span>
</div>
</div>
<span v-else class="disabled">不过期</span>
</td>
@@ -150,12 +165,15 @@ Vue.component("ip-list-table", {
<span v-if="item.reason.length > 0">{{item.reason}}</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">
<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 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/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/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 + '#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>
</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,139 @@
Vue.component("plan-bandwidth-ranges", {
props: ["v-ranges"],
data: function () {
let ranges = this.vRanges
if (ranges == null) {
ranges = []
}
return {
ranges: ranges,
isAdding: false,
minMB: "",
maxMB: "",
pricePerMB: "",
addingRange: {
minMB: 0,
maxMB: 0,
pricePerMB: 0,
totalPrice: 0
}
}
},
methods: {
add: function () {
this.isAdding = !this.isAdding
let that = this
setTimeout(function () {
that.$refs.minMB.focus()
})
},
cancelAdding: function () {
this.isAdding = false
},
confirm: function () {
this.isAdding = false
this.minMB = ""
this.maxMB = ""
this.pricePerMB = ""
this.ranges.push(this.addingRange)
this.ranges.$sort(function (v1, v2) {
if (v1.minMB < v2.minMB) {
return -1
}
if (v1.minMB == v2.minMB) {
return 0
}
return 1
})
this.change()
this.addingRange = {
minMB: 0,
maxMB: 0,
pricePerMB: 0,
totalPrice: 0
}
},
remove: function (index) {
this.ranges.$remove(index)
this.change()
},
change: function () {
this.$emit("change", this.ranges)
}
},
watch: {
minMB: function (v) {
let minMB = parseInt(v.toString())
if (isNaN(minMB) || minMB < 0) {
minMB = 0
}
this.addingRange.minMB = minMB
},
maxMB: function (v) {
let maxMB = parseInt(v.toString())
if (isNaN(maxMB) || maxMB < 0) {
maxMB = 0
}
this.addingRange.maxMB = maxMB
},
pricePerMB: function (v) {
let pricePerMB = parseFloat(v.toString())
if (isNaN(pricePerMB) || pricePerMB < 0) {
pricePerMB = 0
}
this.addingRange.pricePerMB = pricePerMB
}
},
template: `<div>
<!-- 已有价格 -->
<div v-if="ranges.length > 0">
<div class="ui label basic small" v-for="(range, index) in ranges" style="margin-bottom: 0.5em">
{{range.minMB}}MB - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>&infin;</span> &nbsp; 价格:{{range.pricePerMB}}元/MB
&nbsp; <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
</div>
<div class="ui divider"></div>
</div>
<!-- 添加 -->
<div v-if="isAdding">
<table class="ui table">
<tr>
<td class="title">带宽下限</td>
<td>
<div class="ui input right labeled">
<input type="text" placeholder="最小带宽" style="width: 7em" maxlength="10" ref="minMB" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="minMB"/>
<span class="ui label">MB</span>
</div>
</td>
</tr>
<tr>
<td class="title">带宽上限</td>
<td>
<div class="ui input right labeled">
<input type="text" placeholder="最大带宽" style="width: 7em" maxlength="10" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="maxMB"/>
<span class="ui label">MB</span>
</div>
<p class="comment">如果填0表示上不封顶。</p>
</td>
</tr>
<tr>
<td class="title">单位价格</td>
<td>
<div class="ui input right labeled">
<input type="text" placeholder="单位价格" style="width: 7em" maxlength="10" @keyup.enter="confirm()" @keypress.enter.prevent="1" v-model="pricePerMB"/>
<span class="ui label">元/MB</span>
</div>
</td>
</tr>
</table>
<button class="ui button small" type="button" @click.prevent="confirm">确定</button> &nbsp;
<a href="" title="取消" @click.prevent="cancelAdding"><i class="icon remove small"></i></a>
</div>
<!-- 按钮 -->
<div v-if="!isAdding">
<button class="ui button small" type="button" @click.prevent="add">+</button>
</div>
</div>`
})

View File

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

View File

@@ -7,12 +7,28 @@ Vue.component("plan-price-view", {
},
template: `<div>
<span v-if="plan.priceType == 'period'">
<span v-if="plan.monthlyPrice > 0">月度:¥{{plan.monthlyPrice}}元<br/></span>
<span v-if="plan.seasonallyPrice > 0">季度:¥{{plan.seasonallyPrice}}元<br/></span>
<span v-if="plan.yearlyPrice > 0">年度:¥{{plan.yearlyPrice}}元</span>
按时间周期计费
<div>
<span class="grey small">
<span v-if="plan.monthlyPrice > 0">月度:¥{{plan.monthlyPrice}}元<br/></span>
<span v-if="plan.seasonallyPrice > 0">季度:¥{{plan.seasonallyPrice}}元<br/></span>
<span v-if="plan.yearlyPrice > 0">年度:¥{{plan.yearlyPrice}}元</span>
</span>
</div>
</span>
<span v-if="plan.priceType == 'traffic'">
基础价格:¥{{plan.trafficPrice.base}}元/GB
按流量计费
<div>
<span class="grey small">基础价格:¥{{plan.trafficPrice.base}}元/GB</span>
</div>
</span>
<div v-if="plan.priceType == 'bandwidth' && plan.bandwidthPrice != null && plan.bandwidthPrice.percentile > 0">
按{{plan.bandwidthPrice.percentile}}th带宽计费
<div>
<div v-for="range in plan.bandwidthPrice.ranges">
<span class="small grey">{{range.minMB}} - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>&infin;</span> {{range.pricePerMB}}元/MB</span>
</div>
</div>
</div>
</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", {
props: ["v-ip", "v-domain", "v-keyword"],
props: ["v-ip", "v-domain", "v-keyword", "v-cluster-id", "v-node-id"],
data: function () {
let ip = this.vIp
if (ip == null) {
@@ -20,7 +20,8 @@ Vue.component("http-access-log-search-box", {
return {
ip: ip,
domain: domain,
keyword: keyword
keyword: keyword,
clusterId: this.vClusterId
}
},
methods: {
@@ -52,9 +53,12 @@ Vue.component("http-access-log-search-box", {
parent.submit()
}, 500)
}
},
changeCluster: function (clusterId) {
this.clusterId = clusterId
}
},
template: `<div>
template: `<div style="z-index: 10">
<div class="margin"></div>
<div class="ui fields inline">
<div class="ui field">
@@ -79,8 +83,16 @@ Vue.component("http-access-log-search-box", {
</div>
</div>
<slot></slot>
</div>
<div class="ui fields inline" style="margin-top: 0.5em">
<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>`

View File

@@ -193,7 +193,7 @@ Vue.component("http-cache-refs-config-box", {
</table>
<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 class="margin"></div>
</div>`

View File

@@ -76,6 +76,8 @@ Vue.component("http-firewall-actions-box", {
blockScope: "global",
captchaLife: "",
captchaMaxFails: "",
captchaFailBlockTimeout: "",
get302Life: "",
post307Life: "",
recordIPType: "black",
@@ -124,6 +126,22 @@ Vue.component("http-firewall-actions-box", {
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) {
v = parseInt(v)
if (isNaN(v)) {
@@ -190,6 +208,8 @@ Vue.component("http-firewall-actions-box", {
this.blockTimeout = ""
this.blockScope = "global"
this.captchaLife = ""
this.captchaMaxFails = ""
this.captchaFailBlockTimeout = ""
this.get302Life = ""
this.post307Life = ""
@@ -258,6 +278,14 @@ Vue.component("http-firewall-actions-box", {
if (config.options.life != null || config.options.life > 0) {
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
case "notify":
break
@@ -559,7 +587,7 @@ Vue.component("http-firewall-actions-box", {
<td>封锁时间</td>
<td>
<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>
</div>
</td>
@@ -579,19 +607,39 @@ Vue.component("http-firewall-actions-box", {
<td>有效时间</td>
<td>
<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>
</div>
<p class="comment">验证通过后在这个时间内不再验证默认600秒。</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="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 -->
<tr v-if="actionCode == 'get_302'">
<td>有效时间</td>
<td>
<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>
</div>
<p class="comment">验证通过后在这个时间内不再验证。</p>
@@ -603,7 +651,7 @@ Vue.component("http-firewall-actions-box", {
<td>有效时间</td>
<td>
<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>
</div>
<p class="comment">验证通过后在这个时间内不再验证。</p>
@@ -640,7 +688,7 @@ Vue.component("http-firewall-actions-box", {
<td>超时时间</td>
<td>
<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>
</div>
<p class="comment">0表示不超时。</p>

View File

@@ -4,7 +4,8 @@ Vue.component("http-firewall-block-options", {
return {
blockOptions: this.vBlockOptions,
statusCode: this.vBlockOptions.statusCode,
timeout: this.vBlockOptions.timeout
timeout: this.vBlockOptions.timeout,
isEditing: false
}
},
watch: {
@@ -25,9 +26,15 @@ Vue.component("http-firewall-block-options", {
}
}
},
methods: {
edit: function () {
this.isEditing = !this.isEditing
}
},
template: `<div>
<input type="hidden" name="blockOptionsJSON" :value="JSON.stringify(blockOptions)"/>
<table class="ui table">
<input type="hidden" name="blockOptionsJSON" :value="JSON.stringify(blockOptions)"/>
<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>
<td class="title">状态码</td>
<td>

View File

@@ -14,7 +14,7 @@ Vue.component("http-firewall-rule-label", {
},
template: `<div>
<div class="ui label tiny basic">
<div class="ui label tiny basic" style="line-height: 1.5">
{{rule.name}}[{{rule.param}}]
<!-- cc2 -->
@@ -33,6 +33,9 @@ Vue.component("http-firewall-rule-label", {
{{rule.value}}
</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>
</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>
<!-- 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="removeRule(index)"><i class="icon remove"></i></a>
</div>

View File

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

View File

@@ -64,7 +64,7 @@ Vue.component("http-request-conds-box", {
<table class="ui table">
<tr v-for="(group, groupIndex) in conds.groups">
<td class="title" :class="{'color-border':conds.connector == 'and'}" :style="{'border-bottom':(groupIndex < conds.groups.length-1) ? '1px solid rgba(34,36,38,.15)':''}">分组{{groupIndex+1}}</td>
<td style="background: white;" :style="{'border-bottom':(groupIndex < conds.groups.length-1) ? '1px solid rgba(34,36,38,.15)':''}">
<td style="background: white; word-break: break-all" :style="{'border-bottom':(groupIndex < conds.groups.length-1) ? '1px solid rgba(34,36,38,.15)':''}">
<var v-for="(cond, index) in group.conds" style="font-style: normal;display: inline-block; margin-bottom:0.5em">
<span class="ui label tiny">
<var v-if="cond.type.length == 0 || cond.type == 'params'" style="font-style: normal">{{cond.param}} <var>{{cond.operator}}</var></var>

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">
<grey-label v-for="domain in origin.domains">{{domain}}</grey-label>
</div>
<div v-if="origin.hasCert">
<tiny-basic-label>证书</tiny-basic-label>
</div>
</td>
<td :class="{disabled:!origin.isOn}">{{origin.weight}}</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", {
props: [
"v-certs", // 证书列表
"v-cert", // 单个证书
"v-protocol", // 协议https|tls
"v-view-size", // 弹窗尺寸
"v-single-mode" // 单证书模式
"v-view-size", // 弹窗尺寸normal, mini
"v-single-mode", // 单证书模式
"v-description" // 描述文字
],
data: function () {
let certs = this.vCerts
if (certs == null) {
certs = []
}
if (this.vCert != null) {
certs.push(this.vCert)
}
let description = this.vDescription
if (description == null || typeof (description) != "string") {
description = ""
}
return {
certs: certs
certs: certs,
description: description
}
},
methods: {
@@ -77,13 +88,14 @@ Vue.component("ssl-certs-box", {
template: `<div>
<input type="hidden" name="certIdsJSON" :value="JSON.stringify(certIds())"/>
<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>
</div>
<div class="ui divider" v-if="buttonsVisible()"></div>
</div>
<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>
<div v-if="buttonsVisible()">

View File

@@ -73,6 +73,12 @@
padding-bottom: 2em;
overflow-y: auto;
}
@media screen and (max-width: 512px) {
.right-box {
left: 13em;
padding-right: 1em;
}
}
body.expanded .right-box {
left: 10em;
}
@@ -268,6 +274,12 @@ p.margin {
.field.text {
padding: .5em;
}
.form .fields:not(.inline) .field {
margin-bottom: 0.5em !important;
}
.form .fields:not(.inline) .field .button {
min-width: 5em;
}
/** body **/
@keyframes blink {
from {

File diff suppressed because one or more lines are too long

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