Compare commits

...

43 Commits

Author SHA1 Message Date
刘祥超
9731fa35d8 生成components.js 2022-09-04 09:31:50 +08:00
刘祥超
53be4db22b 缓存条件--简单条件增加参数匹配 2022-09-03 19:25:08 +08:00
刘祥超
0202fa2ca3 优化缓存条件设置交互 2022-09-03 19:15:30 +08:00
刘祥超
d5262b5474 简化缓存条件设置 2022-09-03 18:14:34 +08:00
刘祥超
cb2e1d54c2 优化文字提示 2022-09-02 16:34:37 +08:00
刘祥超
6392297a27 修复因URL中含有搜索引擎关键词而导致页面403的问题 2022-09-02 09:26:50 +08:00
刘祥超
6001d4eba3 更新components.js和一处绿色色值 2022-09-01 09:07:28 +08:00
刘祥超
79588e4bbc DDoS防护增加秒级连接速率限制 2022-08-31 10:00:55 +08:00
刘祥超
2b21c38382 访问控制修改为访问鉴权 2022-08-30 17:04:02 +08:00
刘祥超
481d25845e 修复CSRF可能导致访问控制自动保存失败的问题 2022-08-30 12:08:42 +08:00
刘祥超
e0f8bfe283 优化路由规则中访问控制功能 2022-08-30 11:43:49 +08:00
刘祥超
d2ecb01358 修复服务访问控制页面可能空白的问题 2022-08-30 11:42:40 +08:00
刘祥超
316e793b1e 优化访问控制,将“认证”两字改为“鉴权” 2022-08-30 11:22:54 +08:00
刘祥超
6df6809ab3 优化代码 2022-08-29 09:20:46 +08:00
刘祥超
c71f892601 创建用户的时候,可以设置开通默认功能还是全部功能 2022-08-28 20:21:57 +08:00
刘祥超
d0b5a16ce7 更新components.js 2022-08-28 20:01:29 +08:00
刘祥超
84638d3228 优化代码 2022-08-28 15:51:55 +08:00
刘祥超
bb21a2aa5f 优化代码 2022-08-28 11:07:21 +08:00
刘祥超
c37c948129 优化表头排序图标上文字提示 2022-08-27 18:45:07 +08:00
刘祥超
5155476dd7 对缓存策略中的句柄和sendfile增加设置警告 2022-08-27 18:39:36 +08:00
刘祥超
7ef4f60309 服务列表带宽使用新的算法 2022-08-27 18:37:44 +08:00
刘祥超
b0865cbbdc 可以修改服务的CNAME 2022-08-26 19:51:07 +08:00
刘祥超
990c1070e2 DDoS防护增加单IP TCP新连接速率黑名单 2022-08-26 11:32:00 +08:00
刘祥超
65bdd413eb Ln节点可以指定访问IP 2022-08-25 20:36:51 +08:00
刘祥超
60fa35eb73 集群DNS设置中增加”包含Ln节点“选项 2022-08-25 19:18:34 +08:00
刘祥超
32cfd5c233 节点运行日志可以按照节点ID设置为已读 2022-08-25 18:27:15 +08:00
刘祥超
7852495527 优化代码 2022-08-25 16:02:26 +08:00
刘祥超
3679a78f47 WAF增加Javascript cookie验证 2022-08-25 15:35:16 +08:00
刘祥超
6b21568408 优化文字提示 2022-08-25 12:02:48 +08:00
刘祥超
04a5aa41d7 浏览访问日志时自动用点符号标记有数据的分表 2022-08-25 11:36:18 +08:00
刘祥超
fea1a2199c WAF cc2阈值设置太低时提示用户 2022-08-25 09:26:06 +08:00
刘祥超
9c8492efb9 增加测试用例 2022-08-25 09:25:52 +08:00
刘祥超
20b89a8ddd 相关区域名称可以显示别名 2022-08-23 19:15:44 +08:00
刘祥超
5a8e281fb1 优化代码 2022-08-23 14:55:06 +08:00
刘祥超
a9bb413199 NS节点基本的DDoS防护 2022-08-22 15:12:10 +08:00
刘祥超
428d8ab1b1 折叠访问日志选项 2022-08-22 11:16:01 +08:00
刘祥超
c2675bcdb6 优化代码 2022-08-21 20:47:29 +08:00
刘祥超
1fe228e4c0 连接API时,自动将本地的API节点地址转换为回路地址 2022-08-20 20:33:28 +08:00
刘祥超
c5a6497f10 优化代码 2022-08-20 20:31:00 +08:00
刘祥超
f25a82585f 访问数据看板时自动初始化左侧菜单Badge 2022-08-18 09:33:14 +08:00
刘祥超
01209b66ac 对运行日志和IP名单进行操作时,及时更新左侧菜单Badge 2022-08-18 09:28:09 +08:00
刘祥超
4e19817d6f 版本修改为0.5.2 2022-08-17 18:59:37 +08:00
刘祥超
a48adff8ac 版本修改为0.5.1 2022-08-15 19:38:31 +08:00
117 changed files with 3251 additions and 485 deletions

View File

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

View File

@@ -10,7 +10,7 @@ import (
"time" "time"
) )
// 生成Token // Generate 生成Token
func Generate() string { func Generate() string {
timestamp := strconv.FormatInt(time.Now().Unix(), 10) timestamp := strconv.FormatInt(time.Now().Unix(), 10)
@@ -23,7 +23,7 @@ func Generate() string {
return token return token
} }
// 校验Token // Validate 校验Token
func Validate(token string) (b bool) { func Validate(token string) (b bool) {
if len(token) == 0 { if len(token) == 0 {
return return

View File

@@ -11,6 +11,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils" "github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -19,7 +20,9 @@ import (
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/encoding/gzip" "google.golang.org/grpc/encoding/gzip"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"net"
"net/url" "net/url"
"strings"
"sync" "sync"
"time" "time"
) )
@@ -38,7 +41,7 @@ func NewRPCClient(apiConfig *configs.APIConfig, isPrimary bool) (*RPCClient, err
return nil, errors.New("api config should not be nil") return nil, errors.New("api config should not be nil")
} }
client := &RPCClient{ var client = &RPCClient{
apiConfig: apiConfig, apiConfig: apiConfig,
} }
@@ -123,6 +126,10 @@ func (this *RPCClient) ServerRPC() pb.ServerServiceClient {
return pb.NewServerServiceClient(this.pickConn()) return pb.NewServerServiceClient(this.pickConn())
} }
func (this *RPCClient) ServerBandwidthStatRPC() pb.ServerBandwidthStatServiceClient {
return pb.NewServerBandwidthStatServiceClient(this.pickConn())
}
func (this *RPCClient) ServerClientSystemMonthlyStatRPC() pb.ServerClientSystemMonthlyStatServiceClient { func (this *RPCClient) ServerClientSystemMonthlyStatRPC() pb.ServerClientSystemMonthlyStatServiceClient {
return pb.NewServerClientSystemMonthlyStatServiceClient(this.pickConn()) return pb.NewServerClientSystemMonthlyStatServiceClient(this.pickConn())
} }
@@ -320,6 +327,10 @@ func (this *RPCClient) IPLibraryFileRPC() pb.IPLibraryFileServiceClient {
return pb.NewIPLibraryFileServiceClient(this.pickConn()) return pb.NewIPLibraryFileServiceClient(this.pickConn())
} }
func (this *RPCClient) IPLibraryArtifactRPC() pb.IPLibraryArtifactServiceClient {
return pb.NewIPLibraryArtifactServiceClient(this.pickConn())
}
func (this *RPCClient) IPListRPC() pb.IPListServiceClient { func (this *RPCClient) IPListRPC() pb.IPListServiceClient {
return pb.NewIPListServiceClient(this.pickConn()) return pb.NewIPListServiceClient(this.pickConn())
} }
@@ -486,8 +497,8 @@ func (this *RPCClient) TrafficDailyStatRPC() pb.TrafficDailyStatServiceClient {
// Context 构造Admin上下文 // Context 构造Admin上下文
func (this *RPCClient) Context(adminId int64) context.Context { func (this *RPCClient) Context(adminId int64) context.Context {
ctx := context.Background() var ctx = context.Background()
m := maps.Map{ var m = maps.Map{
"timestamp": time.Now().Unix(), "timestamp": time.Now().Unix(),
"type": "admin", "type": "admin",
"userId": adminId, "userId": adminId,
@@ -502,15 +513,15 @@ func (this *RPCClient) Context(adminId int64) context.Context {
utils.PrintError(err) utils.PrintError(err)
return context.Background() return context.Background()
} }
token := base64.StdEncoding.EncodeToString(data) var token = base64.StdEncoding.EncodeToString(data)
ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token) ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token)
return ctx return ctx
} }
// APIContext 构造API上下文 // APIContext 构造API上下文
func (this *RPCClient) APIContext(apiNodeId int64) context.Context { func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
ctx := context.Background() var ctx = context.Background()
m := maps.Map{ var m = maps.Map{
"timestamp": time.Now().Unix(), "timestamp": time.Now().Unix(),
"type": "api", "type": "api",
"userId": apiNodeId, "userId": apiNodeId,
@@ -525,7 +536,7 @@ func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
utils.PrintError(err) utils.PrintError(err)
return context.Background() return context.Background()
} }
token := base64.StdEncoding.EncodeToString(data) var token = base64.StdEncoding.EncodeToString(data)
ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token) ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token)
return ctx return ctx
} }
@@ -542,20 +553,39 @@ func (this *RPCClient) UpdateConfig(config *configs.APIConfig) error {
// 初始化 // 初始化
func (this *RPCClient) init() error { func (this *RPCClient) init() error {
// 当前的IP地址
var localIPAddrs = this.localIPAddrs()
// 重新连接 // 重新连接
conns := []*grpc.ClientConn{} var conns = []*grpc.ClientConn{}
for _, endpoint := range this.apiConfig.RPC.Endpoints { for _, endpoint := range this.apiConfig.RPC.Endpoints {
u, err := url.Parse(endpoint) u, err := url.Parse(endpoint)
if err != nil { if err != nil {
return errors.New("parse endpoint failed: " + err.Error()) return errors.New("parse endpoint failed: " + err.Error())
} }
var apiHost = u.Host
// 如果本机,则将地址修改为回路地址
if lists.ContainsString(localIPAddrs, u.Hostname()) {
if strings.Contains(apiHost, "[") { // IPv6 [host]:port
apiHost = "[::1]"
} else {
apiHost = "127.0.0.1"
}
var port = u.Port()
if len(port) > 0 {
apiHost += ":" + port
}
}
var conn *grpc.ClientConn var conn *grpc.ClientConn
var callOptions = grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(128*1024*1024), var callOptions = grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(128*1024*1024),
grpc.UseCompressor(gzip.Name)) grpc.UseCompressor(gzip.Name))
if u.Scheme == "http" { if u.Scheme == "http" {
conn, err = grpc.Dial(u.Host, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions) conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions)
} else if u.Scheme == "https" { } else if u.Scheme == "https" {
conn, err = grpc.Dial(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true, InsecureSkipVerify: true,
})), callOptions) })), callOptions)
} else { } else {
@@ -582,7 +612,7 @@ func (this *RPCClient) pickConn() *grpc.ClientConn {
// 检查连接状态 // 检查连接状态
if len(this.conns) > 0 { if len(this.conns) > 0 {
availableConns := []*grpc.ClientConn{} var availableConns = []*grpc.ClientConn{}
for _, state := range []connectivity.State{connectivity.Ready, connectivity.Idle, connectivity.Connecting} { for _, state := range []connectivity.State{connectivity.Ready, connectivity.Idle, connectivity.Connecting} {
for _, conn := range this.conns { for _, conn := range this.conns {
if conn.GetState() == state { if conn.GetState() == state {
@@ -634,3 +664,18 @@ func (this *RPCClient) Close() error {
return lastErr return lastErr
} }
func (this *RPCClient) localIPAddrs() []string {
localInterfaceAddrs, err := net.InterfaceAddrs()
var localIPAddrs = []string{}
if err == nil {
for _, addr := range localInterfaceAddrs {
var addrString = addr.String()
var index = strings.Index(addrString, "/")
if index > 0 {
localIPAddrs = append(localIPAddrs, addrString[:index])
}
}
}
return localIPAddrs
}

View File

@@ -35,7 +35,8 @@ func TestRPCClient_NodeRPC(t *testing.T) {
func TestRPC_Dial_HTTP(t *testing.T) { func TestRPC_Dial_HTTP(t *testing.T) {
client, err := NewRPCClient(&configs.APIConfig{ client, err := NewRPCClient(&configs.APIConfig{
RPC: struct { RPC: struct {
Endpoints []string `yaml:"endpoints"` Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
}{ }{
Endpoints: []string{"http://127.0.0.1:8004"}, Endpoints: []string{"http://127.0.0.1:8004"},
}, },
@@ -56,7 +57,8 @@ func TestRPC_Dial_HTTP(t *testing.T) {
func TestRPC_Dial_HTTP_2(t *testing.T) { func TestRPC_Dial_HTTP_2(t *testing.T) {
client, err := NewRPCClient(&configs.APIConfig{ client, err := NewRPCClient(&configs.APIConfig{
RPC: struct { RPC: struct {
Endpoints []string `yaml:"endpoints"` Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
}{ }{
Endpoints: []string{"https://127.0.0.1:8003"}, Endpoints: []string{"https://127.0.0.1:8003"},
}, },
@@ -77,7 +79,8 @@ func TestRPC_Dial_HTTP_2(t *testing.T) {
func TestRPC_Dial_HTTPS(t *testing.T) { func TestRPC_Dial_HTTPS(t *testing.T) {
client, err := NewRPCClient(&configs.APIConfig{ client, err := NewRPCClient(&configs.APIConfig{
RPC: struct { RPC: struct {
Endpoints []string `yaml:"endpoints"` Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
}{ }{
Endpoints: []string{"https://127.0.0.1:8004"}, Endpoints: []string{"https://127.0.0.1:8004"},
}, },
@@ -94,3 +97,53 @@ func TestRPC_Dial_HTTPS(t *testing.T) {
} }
t.Log(resp.Node) t.Log(resp.Node)
} }
func BenchmarkNewRPCClient(b *testing.B) {
config, err := configs.LoadAPIConfig()
if err != nil {
b.Fatal(err)
}
rpc, err := NewRPCClient(config, true)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := rpc.AdminRPC().LoginAdmin(rpc.Context(0), &pb.LoginAdminRequest{
Username: "admin",
Password: stringutil.Md5("123456"),
})
if err != nil {
b.Fatal(err)
}
_ = resp
}
}
func BenchmarkNewRPCClient_2(b *testing.B) {
config, err := configs.LoadAPIConfig()
if err != nil {
b.Fatal(err)
}
rpc, err := NewRPCClient(config, true)
if err != nil {
b.Fatal(err)
}
var conn = rpc.AdminRPC()
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := conn.LoginAdmin(rpc.Context(0), &pb.LoginAdminRequest{
Username: "admin",
Password: stringutil.Md5("123456"),
})
if err != nil {
b.Fatal(err)
}
_ = resp
}
}

View File

@@ -5,7 +5,7 @@ import (
"strings" "strings"
) )
// format address // FormatAddress format address
func FormatAddress(addr string) string { func FormatAddress(addr string) string {
if strings.HasSuffix(addr, "unix:") { if strings.HasSuffix(addr, "unix:") {
return addr return addr
@@ -17,7 +17,7 @@ func FormatAddress(addr string) string {
return addr return addr
} }
// 分割数字 // SplitNumbers 分割数字
func SplitNumbers(numbers string) (result []int64) { func SplitNumbers(numbers string) (result []int64) {
if len(numbers) == 0 { if len(numbers) == 0 {
return return

View File

@@ -0,0 +1,67 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
import (
"github.com/iwind/TeaGo/lists"
"strings"
)
func FilterNotEmpty(item string) bool {
return len(item) > 0
}
func MapAddPrefixFunc(prefix string) func(item string) string {
return func(item string) string {
if !strings.HasPrefix(item, prefix) {
return prefix + item
}
return item
}
}
type StringsStream struct {
s []string
}
func NewStringsStream(s []string) *StringsStream {
return &StringsStream{s: s}
}
func (this *StringsStream) Map(f ...func(item string) string) *StringsStream {
for index, item := range this.s {
for _, f1 := range f {
item = f1(item)
}
this.s[index] = item
}
return this
}
func (this *StringsStream) Filter(f ...func(item string) bool) *StringsStream {
for _, f1 := range f {
var newStrings = []string{}
for _, item := range this.s {
if f1(item) {
newStrings = append(newStrings, item)
}
}
this.s = newStrings
}
return this
}
func (this *StringsStream) Unique() *StringsStream {
var newStrings = []string{}
for _, item := range this.s {
if !lists.ContainsString(newStrings, item) {
newStrings = append(newStrings, item)
}
}
this.s = newStrings
return this
}
func (this *StringsStream) Result() []string {
return this.s
}

View File

@@ -0,0 +1,25 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"strings"
"testing"
)
func TestStringsStream_Filter(t *testing.T) {
var stream = utils.NewStringsStream([]string{"a", "b", "1", "2", "", "png", "a"})
stream.Filter(func(item string) bool {
return len(item) > 0
})
t.Log(stream.Result())
stream.Map(func(item string) string {
return "." + item
})
t.Log(stream.Result())
stream.Unique()
t.Log(stream.Result())
stream.Map(strings.ToUpper, strings.ToLower)
t.Log(stream.Result())
}

View File

@@ -25,7 +25,8 @@ func (this *DetailAction) Init() {
} }
func (this *DetailAction) RunGet(params struct { func (this *DetailAction) RunGet(params struct {
NodeId int64 NodeId int64
ClusterId int64
}) { }) {
this.Data["nodeId"] = params.NodeId this.Data["nodeId"] = params.NodeId
@@ -43,13 +44,13 @@ func (this *DetailAction) RunGet(params struct {
// 主集群 // 主集群
var clusterMap maps.Map = nil var clusterMap maps.Map = nil
if node.NodeCluster != nil { if node.NodeCluster != nil {
clusterId := node.NodeCluster.Id var clusterId = node.NodeCluster.Id
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: clusterId}) clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: clusterId})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
cluster := clusterResp.NodeCluster var cluster = clusterResp.NodeCluster
if cluster != nil { if cluster != nil {
clusterMap = maps.Map{ clusterMap = maps.Map{
"id": cluster.Id, "id": cluster.Id,
@@ -69,6 +70,14 @@ func (this *DetailAction) RunGet(params struct {
}) })
} }
// 当前访问集群的DNS设置
clusterDNSInfo, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["dnsIsExcludingLnNode"] = clusterDNSInfo != nil && !clusterDNSInfo.IncludingLnNodes && node.Level > 1
// IP地址 // IP地址
ipAddressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledNodeIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledNodeIPAddressesWithNodeIdRequest{ ipAddressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledNodeIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledNodeIPAddressesWithNodeIdRequest{
NodeId: params.NodeId, NodeId: params.NodeId,
@@ -286,6 +295,11 @@ func (this *DetailAction) RunGet(params struct {
} }
} }
var lnAddrs = node.LnAddrs
if lnAddrs == nil {
lnAddrs = []string{}
}
this.Data["node"] = maps.Map{ this.Data["node"] = maps.Map{
"id": node.Id, "id": node.Id,
"name": node.Name, "name": node.Name,
@@ -303,6 +317,7 @@ func (this *DetailAction) RunGet(params struct {
"routes": routeMaps, "routes": routeMaps,
"level": node.Level, "level": node.Level,
"levelInfo": nodeconfigs.FindNodeLevel(int(node.Level)), "levelInfo": nodeconfigs.FindNodeLevel(int(node.Level)),
"lnAddrs": lnAddrs,
"status": maps.Map{ "status": maps.Map{
"isActive": status.IsActive, "isActive": status.IsActive,

View File

@@ -102,8 +102,12 @@ func (this *IndexAction) RunPost(params struct {
this.FailField("tcpMaxConnectionsPerIP", "TCP: 单IP TCP最大连接数不能小于"+types.String(nodeconfigs.DefaultTCPMinConnectionsPerIP)) this.FailField("tcpMaxConnectionsPerIP", "TCP: 单IP TCP最大连接数不能小于"+types.String(nodeconfigs.DefaultTCPMinConnectionsPerIP))
} }
if tcpConfig.NewConnectionsRate > 0 && tcpConfig.NewConnectionsRate < nodeconfigs.DefaultTCPNewConnectionsMinRate { if tcpConfig.NewConnectionsMinutelyRate > 0 && tcpConfig.NewConnectionsMinutelyRate < nodeconfigs.DefaultTCPNewConnectionsMinMinutelyRate {
this.FailField("tcpNewConnectionsRate", "TCP: 单IP连接速率不能小于"+types.String(nodeconfigs.DefaultTCPNewConnectionsMinRate)) this.FailField("tcpNewConnectionsMinutelyRate", "TCP: 单IP连接速率不能小于"+types.String(nodeconfigs.DefaultTCPNewConnectionsMinMinutelyRate))
}
if tcpConfig.NewConnectionsSecondlyRate > 0 && tcpConfig.NewConnectionsSecondlyRate < nodeconfigs.DefaultTCPNewConnectionsMinSecondlyRate {
this.FailField("tcpNewConnectionsSecondlyRate", "TCP: 单IP连接速率不能小于"+types.String(nodeconfigs.DefaultTCPNewConnectionsMinSecondlyRate))
} }
// Port // Port

View File

@@ -11,6 +11,7 @@ import (
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"net"
) )
type UpdateAction struct { type UpdateAction struct {
@@ -61,7 +62,7 @@ func (this *UpdateAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
ipAddressMaps := []maps.Map{} var ipAddressMaps = []maps.Map{}
for _, addr := range ipAddressesResp.NodeIPAddresses { for _, addr := range ipAddressesResp.NodeIPAddresses {
thresholds, err := ipaddressutils.InitNodeIPAddressThresholds(this.Parent(), addr.Id) thresholds, err := ipaddressutils.InitNodeIPAddressThresholds(this.Parent(), addr.Id)
if err != nil { if err != nil {
@@ -109,6 +110,12 @@ func (this *UpdateAction) RunGet(params struct {
"level": node.Level, "level": node.Level,
} }
if node.LnAddrs == nil {
nodeMap["lnAddrs"] = []string{}
} else {
nodeMap["lnAddrs"] = node.LnAddrs
}
if node.NodeCluster != nil { if node.NodeCluster != nil {
nodeMap["primaryCluster"] = maps.Map{ nodeMap["primaryCluster"] = maps.Map{
"id": node.NodeCluster.Id, "id": node.NodeCluster.Id,
@@ -149,6 +156,7 @@ func (this *UpdateAction) RunPost(params struct {
SecondaryClusterIds []byte SecondaryClusterIds []byte
IsOn bool IsOn bool
Level int32 Level int32
LnAddrs []string
Must *actions.Must Must *actions.Must
}) { }) {
@@ -178,7 +186,7 @@ func (this *UpdateAction) RunPost(params struct {
} }
// IP地址 // IP地址
ipAddresses := []maps.Map{} var ipAddresses = []maps.Map{}
if len(params.IPAddressesJSON) > 0 { if len(params.IPAddressesJSON) > 0 {
err := json.Unmarshal(params.IPAddressesJSON, &ipAddresses) err := json.Unmarshal(params.IPAddressesJSON, &ipAddresses)
if err != nil { if err != nil {
@@ -195,6 +203,27 @@ func (this *UpdateAction) RunPost(params struct {
this.Fail("没有权限修改节点级别:" + types.String(params.Level)) this.Fail("没有权限修改节点级别:" + types.String(params.Level))
} }
// 检查Ln节点地址
var lnAddrs = []string{}
if params.Level > 1 {
for _, lnAddr := range params.LnAddrs {
if len(lnAddr) == 0 {
continue
}
// 处理 host:port
host, _, err := net.SplitHostPort(lnAddr)
if err == nil {
lnAddr = host
}
if net.ParseIP(lnAddr) == nil {
this.Fail("L2级别访问地址 '" + lnAddr + "' 格式错误,请纠正后再提交")
}
lnAddrs = append(lnAddrs, lnAddr)
}
}
_, err := this.RPC().NodeRPC().UpdateNode(this.AdminContext(), &pb.UpdateNodeRequest{ _, err := this.RPC().NodeRPC().UpdateNode(this.AdminContext(), &pb.UpdateNodeRequest{
NodeId: params.NodeId, NodeId: params.NodeId,
NodeGroupId: params.GroupId, NodeGroupId: params.GroupId,
@@ -204,6 +233,7 @@ func (this *UpdateAction) RunPost(params struct {
SecondaryNodeClusterIds: secondaryClusterIds, SecondaryNodeClusterIds: secondaryClusterIds,
IsOn: params.IsOn, IsOn: params.IsOn,
Level: params.Level, Level: params.Level,
LnAddrs: lnAddrs,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -75,8 +75,12 @@ func (this *IndexAction) RunPost(params struct {
this.FailField("tcpMaxConnectionsPerIP", "TCP: 单IP TCP最大连接数不能小于"+types.String(nodeconfigs.DefaultTCPMinConnectionsPerIP)) this.FailField("tcpMaxConnectionsPerIP", "TCP: 单IP TCP最大连接数不能小于"+types.String(nodeconfigs.DefaultTCPMinConnectionsPerIP))
} }
if tcpConfig.NewConnectionsRate > 0 && tcpConfig.NewConnectionsRate < nodeconfigs.DefaultTCPNewConnectionsMinRate { if tcpConfig.NewConnectionsMinutelyRate > 0 && tcpConfig.NewConnectionsMinutelyRate < nodeconfigs.DefaultTCPNewConnectionsMinMinutelyRate {
this.FailField("tcpNewConnectionsRate", "TCP: 单IP连接速率不能小于"+types.String(nodeconfigs.DefaultTCPNewConnectionsMinRate)) this.FailField("tcpNewConnectionsMinutelyRate", "TCP: 单IP连接速率不能小于"+types.String(nodeconfigs.DefaultTCPNewConnectionsMinMinutelyRate))
}
if tcpConfig.NewConnectionsSecondlyRate > 0 && tcpConfig.NewConnectionsSecondlyRate < nodeconfigs.DefaultTCPNewConnectionsMinSecondlyRate {
this.FailField("tcpNewConnectionsSecondlyRate", "TCP: 单IP连接速率不能小于"+types.String(nodeconfigs.DefaultTCPNewConnectionsMinSecondlyRate))
} }
// Port // Port

View File

@@ -53,6 +53,7 @@ func (this *IndexAction) RunGet(params struct {
} }
this.Data["ttl"] = dnsInfoResp.Ttl this.Data["ttl"] = dnsInfoResp.Ttl
this.Data["cnameAsDomain"] = dnsInfoResp.CnameAsDomain this.Data["cnameAsDomain"] = dnsInfoResp.CnameAsDomain
this.Data["includingLnNodes"] = dnsInfoResp.IncludingLnNodes
this.Show() this.Show()
} }
@@ -60,13 +61,14 @@ func (this *IndexAction) RunGet(params struct {
func (this *IndexAction) RunPost(params struct { func (this *IndexAction) RunPost(params struct {
ClusterId int64 ClusterId int64
DnsDomainId int64 DnsDomainId int64
DnsName string DnsName string
NodesAutoSync bool NodesAutoSync bool
ServersAutoSync bool ServersAutoSync bool
CnameRecords []string CnameRecords []string
Ttl int32 Ttl int32
CnameAsDomain bool CnameAsDomain bool
IncludingLnNodes bool
ConfirmResetDomain bool // 是否确认重置域名 ConfirmResetDomain bool // 是否确认重置域名
@@ -107,14 +109,15 @@ func (this *IndexAction) RunPost(params struct {
} }
_, err := this.RPC().NodeClusterRPC().UpdateNodeClusterDNS(this.AdminContext(), &pb.UpdateNodeClusterDNSRequest{ _, err := this.RPC().NodeClusterRPC().UpdateNodeClusterDNS(this.AdminContext(), &pb.UpdateNodeClusterDNSRequest{
NodeClusterId: params.ClusterId, NodeClusterId: params.ClusterId,
DnsName: params.DnsName, DnsName: params.DnsName,
DnsDomainId: params.DnsDomainId, DnsDomainId: params.DnsDomainId,
NodesAutoSync: params.NodesAutoSync, NodesAutoSync: params.NodesAutoSync,
ServersAutoSync: params.ServersAutoSync, ServersAutoSync: params.ServersAutoSync,
CnameRecords: params.CnameRecords, CnameRecords: params.CnameRecords,
Ttl: params.Ttl, Ttl: params.Ttl,
CnameAsDomain: params.CnameAsDomain, CnameAsDomain: params.CnameAsDomain,
IncludingLnNodes: params.IncludingLnNodes,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -4,6 +4,7 @@ package logs
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"strings" "strings"
@@ -29,5 +30,8 @@ func (this *FixAction) RunPost(params struct {
return return
} }
// 通知左侧数字Badge更新
helpers.NotifyNodeLogsCountChange()
this.Success() this.Success()
} }

View File

@@ -4,6 +4,7 @@ package logs
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
) )
@@ -21,5 +22,8 @@ func (this *FixAllAction) RunPost(params struct {
return return
} }
// 通知左侧数字Badge更新
helpers.NotifyNodeLogsCountChange()
this.Success() this.Success()
} }

View File

@@ -120,6 +120,8 @@ func (this *IndexAction) RunGet(params struct {
return return
} }
var firstUnreadNodeMap maps.Map = nil
var logs = []maps.Map{} var logs = []maps.Map{}
for _, log := range logsResp.NodeLogs { for _, log := range logsResp.NodeLogs {
// 节点信息 // 节点信息
@@ -132,6 +134,13 @@ func (this *IndexAction) RunGet(params struct {
continue continue
} }
if params.Type == "unread" && firstUnreadNodeMap == nil {
firstUnreadNodeMap = maps.Map{
"id": node.Id,
"name": node.Name,
}
}
// 服务信息 // 服务信息
var serverMap = maps.Map{"id": 0} var serverMap = maps.Map{"id": 0}
if log.ServerId > 0 { if log.ServerId > 0 {
@@ -174,5 +183,7 @@ func (this *IndexAction) RunGet(params struct {
} }
this.Data["logs"] = logs this.Data["logs"] = logs
this.Data["firstUnreadNode"] = firstUnreadNodeMap
this.Show() this.Show()
} }

View File

@@ -4,6 +4,7 @@ package logs
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
) )
@@ -20,5 +21,8 @@ func (this *ReadAllLogsAction) RunPost(params struct {
return return
} }
// 通知左侧数字Badge更新
helpers.NotifyNodeLogsCountChange()
this.Success() this.Success()
} }

View File

@@ -4,6 +4,8 @@ package logs
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
) )
@@ -13,14 +15,21 @@ type ReadLogsAction struct {
func (this *ReadLogsAction) RunPost(params struct { func (this *ReadLogsAction) RunPost(params struct {
LogIds []int64 LogIds []int64
NodeId int64
}) { }) {
_, err := this.RPC().NodeLogRPC().UpdateNodeLogsRead(this.AdminContext(), &pb.UpdateNodeLogsReadRequest{ _, err := this.RPC().NodeLogRPC().UpdateNodeLogsRead(this.AdminContext(), &pb.UpdateNodeLogsReadRequest{
NodeLogIds: params.LogIds, NodeLogIds: params.LogIds,
NodeId: params.NodeId,
Role: nodeconfigs.NodeRoleNode,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
// 通知左侧数字Badge更新
helpers.NotifyNodeLogsCountChange()
this.Success() this.Success()
} }

View File

@@ -9,6 +9,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils" "github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dashboard/dashboardutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dashboard/dashboardutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
@@ -25,6 +26,10 @@ func (this *IndexAction) Init() {
} }
func (this *IndexAction) RunGet(params struct{}) { func (this *IndexAction) RunGet(params struct{}) {
// 通知菜单数字Badge更新
helpers.NotifyIPItemsCountChanges()
helpers.NotifyNodeLogsCountChange()
if teaconst.IsPlus { if teaconst.IsPlus {
this.RedirectURL("/dashboard/boards") this.RedirectURL("/dashboard/boards")
return return

View File

@@ -23,36 +23,38 @@ func (this *UpdateClusterPopupAction) RunGet(params struct {
}) { }) {
this.Data["clusterId"] = params.ClusterId this.Data["clusterId"] = params.ClusterId
dnsResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId}) dnsInfoResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
this.Data["dnsName"] = dnsResp.Name this.Data["dnsName"] = dnsInfoResp.Name
this.Data["nodesAutoSync"] = dnsResp.NodesAutoSync this.Data["nodesAutoSync"] = dnsInfoResp.NodesAutoSync
this.Data["serversAutoSync"] = dnsResp.ServersAutoSync this.Data["serversAutoSync"] = dnsInfoResp.ServersAutoSync
if dnsResp.Domain != nil { if dnsInfoResp.Domain != nil {
this.Data["domainId"] = dnsResp.Domain.Id this.Data["domainId"] = dnsInfoResp.Domain.Id
this.Data["domain"] = dnsResp.Domain.Name this.Data["domain"] = dnsInfoResp.Domain.Name
} else { } else {
this.Data["domainId"] = 0 this.Data["domainId"] = 0
this.Data["domain"] = "" this.Data["domain"] = ""
} }
if dnsResp.Provider != nil { if dnsInfoResp.Provider != nil {
this.Data["providerType"] = dnsResp.Provider.Type this.Data["providerType"] = dnsInfoResp.Provider.Type
this.Data["providerId"] = dnsResp.Provider.Id this.Data["providerId"] = dnsInfoResp.Provider.Id
} else { } else {
this.Data["providerType"] = "" this.Data["providerType"] = ""
this.Data["providerId"] = 0 this.Data["providerId"] = 0
} }
if len(dnsResp.CnameRecords) == 0 { if len(dnsInfoResp.CnameRecords) == 0 {
this.Data["cnameRecords"] = []string{} this.Data["cnameRecords"] = []string{}
} else { } else {
this.Data["cnameRecords"] = dnsResp.CnameRecords this.Data["cnameRecords"] = dnsInfoResp.CnameRecords
} }
this.Data["ttl"] = dnsResp.Ttl this.Data["ttl"] = dnsInfoResp.Ttl
this.Data["cnameAsDomain"] = dnsInfoResp.CnameAsDomain
this.Data["includingLnNodes"] = dnsInfoResp.IncludingLnNodes
// 所有服务商 // 所有服务商
providerTypesResp, err := this.RPC().DNSProviderRPC().FindAllDNSProviderTypes(this.AdminContext(), &pb.FindAllDNSProviderTypesRequest{}) providerTypesResp, err := this.RPC().DNSProviderRPC().FindAllDNSProviderTypes(this.AdminContext(), &pb.FindAllDNSProviderTypesRequest{})
@@ -73,13 +75,15 @@ func (this *UpdateClusterPopupAction) RunGet(params struct {
} }
func (this *UpdateClusterPopupAction) RunPost(params struct { func (this *UpdateClusterPopupAction) RunPost(params struct {
ClusterId int64 ClusterId int64
DnsName string DnsName string
DomainId int64 DomainId int64
NodesAutoSync bool NodesAutoSync bool
ServersAutoSync bool ServersAutoSync bool
CnameRecords []string CnameRecords []string
Ttl int32 Ttl int32
CnameAsDomain bool
IncludingLnNodes bool
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
@@ -108,13 +112,15 @@ func (this *UpdateClusterPopupAction) RunPost(params struct {
} }
_, err = this.RPC().NodeClusterRPC().UpdateNodeClusterDNS(this.AdminContext(), &pb.UpdateNodeClusterDNSRequest{ _, err = this.RPC().NodeClusterRPC().UpdateNodeClusterDNS(this.AdminContext(), &pb.UpdateNodeClusterDNSRequest{
NodeClusterId: params.ClusterId, NodeClusterId: params.ClusterId,
DnsName: params.DnsName, DnsName: params.DnsName,
DnsDomainId: params.DomainId, DnsDomainId: params.DomainId,
NodesAutoSync: params.NodesAutoSync, NodesAutoSync: params.NodesAutoSync,
ServersAutoSync: params.ServersAutoSync, ServersAutoSync: params.ServersAutoSync,
CnameRecords: params.CnameRecords, CnameRecords: params.CnameRecords,
Ttl: params.Ttl, Ttl: params.Ttl,
CnameAsDomain: params.CnameAsDomain,
IncludingLnNodes: params.IncludingLnNodes,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -58,7 +58,7 @@ func (this *IndexAction) RunGet(params struct {
this.Data["isUser"] = false this.Data["isUser"] = false
this.Data["menu"] = "signIn" this.Data["menu"] = "signIn"
timestamp := fmt.Sprintf("%d", time.Now().Unix()) var timestamp = fmt.Sprintf("%d", time.Now().Unix())
this.Data["token"] = stringutil.Md5(TokenSalt+timestamp) + timestamp this.Data["token"] = stringutil.Md5(TokenSalt+timestamp) + timestamp
this.Data["from"] = params.From this.Data["from"] = params.From
@@ -111,11 +111,11 @@ func (this *IndexAction) RunPost(params struct {
if len(params.Token) <= 32 { if len(params.Token) <= 32 {
this.Fail("请通过登录页面登录") this.Fail("请通过登录页面登录")
} }
timestampString := params.Token[32:] var timestampString = params.Token[32:]
if stringutil.Md5(TokenSalt+timestampString) != params.Token[:32] { if stringutil.Md5(TokenSalt+timestampString) != params.Token[:32] {
this.FailField("refresh", "登录页面已过期,请刷新后重试") this.FailField("refresh", "登录页面已过期,请刷新后重试")
} }
timestamp := types.Int64(timestampString) var timestamp = types.Int64(timestampString)
if timestamp < time.Now().Unix()-1800 { if timestamp < time.Now().Unix()-1800 {
this.FailField("refresh", "登录页面已过期,请刷新后重试") this.FailField("refresh", "登录页面已过期,请刷新后重试")
} }
@@ -157,7 +157,7 @@ func (this *IndexAction) RunPost(params struct {
return return
} }
if otpLoginResp.Login != nil && otpLoginResp.Login.IsOn { if otpLoginResp.Login != nil && otpLoginResp.Login.IsOn {
loginParams := maps.Map{} var loginParams = maps.Map{}
err = json.Unmarshal(otpLoginResp.Login.ParamsJSON, &loginParams) err = json.Unmarshal(otpLoginResp.Login.ParamsJSON, &loginParams)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
@@ -169,7 +169,7 @@ func (this *IndexAction) RunPost(params struct {
} }
} }
adminId := resp.AdminId var adminId = resp.AdminId
params.Auth.StoreAdmin(adminId, params.Remember) params.Auth.StoreAdmin(adminId, params.Remember)
// 记录日志 // 记录日志

View File

@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"github.com/tealeg/xlsx/v3" "github.com/tealeg/xlsx/v3"
"strconv" "strconv"
@@ -69,17 +68,13 @@ func (this *ExportExcelAction) RunGet(params struct {
return return
} }
if regionResp.IpRegion != nil { if regionResp.IpRegion != nil {
pieces := []string{} regionName = regionResp.IpRegion.Summary
if len(regionResp.IpRegion.Country) > 0 {
pieces = append(pieces, regionResp.IpRegion.Country) // remove isp from regionName
var index = strings.LastIndex(regionName, "|")
if index > 0 {
regionName = regionName[:index]
} }
if len(regionResp.IpRegion.Province) > 0 && !lists.ContainsString(pieces, regionResp.IpRegion.Province) {
pieces = append(pieces, regionResp.IpRegion.Province)
}
if len(regionResp.IpRegion.City) > 0 && !lists.ContainsString(pieces, regionResp.IpRegion.City) && !lists.ContainsString(pieces, strings.TrimSuffix(regionResp.IpRegion.Province, "市")) {
pieces = append(pieces, regionResp.IpRegion.City)
}
regionName = strings.Join(pieces, " ")
if len(regionResp.IpRegion.Isp) > 0 { if len(regionResp.IpRegion.Isp) > 0 {
ispName = regionResp.IpRegion.Isp ispName = regionResp.IpRegion.Isp

View File

@@ -4,10 +4,8 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders" "github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"strings"
) )
type IndexAction struct { type IndexAction struct {
@@ -72,11 +70,7 @@ func (this *IndexAction) RunGet(params struct {
return return
} }
if regionResp.IpRegion != nil { if regionResp.IpRegion != nil {
pieces := []string{regionResp.IpRegion.Summary} regionName = regionResp.IpRegion.Summary
if len(regionResp.IpRegion.Isp) > 0 && !lists.ContainsString(pieces, regionResp.IpRegion.Isp) {
pieces = append(pieces, "| "+regionResp.IpRegion.Isp)
}
regionName = strings.Join(pieces, " ")
} }
logMaps = append(logMaps, maps.Map{ logMaps = append(logMaps, maps.Map{

View File

@@ -36,7 +36,7 @@ func (this *IndexAction) RunGet(params struct {
this.NotFound("firewallPolicy", params.FirewallPolicyId) this.NotFound("firewallPolicy", params.FirewallPolicyId)
return return
} }
selectedCountryIds := []int64{} var selectedCountryIds = []int64{}
if policyConfig.Inbound != nil && policyConfig.Inbound.Region != nil { if policyConfig.Inbound != nil && policyConfig.Inbound.Region != nil {
selectedCountryIds = policyConfig.Inbound.Region.DenyCountryIds selectedCountryIds = policyConfig.Inbound.Region.DenyCountryIds
} }
@@ -46,11 +46,11 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
countryMaps := []maps.Map{} var countryMaps = []maps.Map{}
for _, country := range countriesResp.RegionCountries { for _, country := range countriesResp.RegionCountries {
countryMaps = append(countryMaps, maps.Map{ countryMaps = append(countryMaps, maps.Map{
"id": country.Id, "id": country.Id,
"name": country.Name, "name": country.DisplayName,
"letter": strings.ToUpper(string(country.Pinyin[0][0])), "letter": strings.ToUpper(string(country.Pinyin[0][0])),
"isChecked": lists.ContainsInt64(selectedCountryIds, country.Id), "isChecked": lists.ContainsInt64(selectedCountryIds, country.Id),
}) })

View File

@@ -37,7 +37,7 @@ func (this *ProvincesAction) RunGet(params struct {
this.NotFound("firewallPolicy", params.FirewallPolicyId) this.NotFound("firewallPolicy", params.FirewallPolicyId)
return return
} }
selectedProvinceIds := []int64{} var selectedProvinceIds = []int64{}
if policyConfig.Inbound != nil && policyConfig.Inbound.Region != nil { if policyConfig.Inbound != nil && policyConfig.Inbound.Region != nil {
selectedProvinceIds = policyConfig.Inbound.Region.DenyProvinceIds selectedProvinceIds = policyConfig.Inbound.Region.DenyProvinceIds
} }
@@ -49,11 +49,11 @@ func (this *ProvincesAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
provinceMaps := []maps.Map{} var provinceMaps = []maps.Map{}
for _, province := range provincesResp.RegionProvinces { for _, province := range provincesResp.RegionProvinces {
provinceMaps = append(provinceMaps, maps.Map{ provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id, "id": province.Id,
"name": province.Name, "name": province.DisplayName,
"isChecked": lists.ContainsInt64(selectedProvinceIds, province.Id), "isChecked": lists.ContainsInt64(selectedProvinceIds, province.Id),
}) })
} }

View File

@@ -55,7 +55,7 @@ func (this *CountriesAction) RunGet(params struct {
for _, country := range countriesResp.RegionCountries { for _, country := range countriesResp.RegionCountries {
countryMaps = append(countryMaps, maps.Map{ countryMaps = append(countryMaps, maps.Map{
"id": country.Id, "id": country.Id,
"name": country.Name, "name": country.DisplayName,
"letter": strings.ToUpper(string(country.Pinyin[0][0])), "letter": strings.ToUpper(string(country.Pinyin[0][0])),
"isChecked": lists.ContainsInt64(selectedCountryIds, country.Id), "isChecked": lists.ContainsInt64(selectedCountryIds, country.Id),
}) })

View File

@@ -46,7 +46,7 @@ func (this *ProvincesAction) RunGet(params struct {
selectedProvinceIds = policyConfig.Inbound.Region.DenyProvinceIds selectedProvinceIds = policyConfig.Inbound.Region.DenyProvinceIds
} }
provincesResp, err := this.RPC().RegionProvinceRPC().FindAllRegionProvincesWithCountryId(this.AdminContext(), &pb.FindAllRegionProvincesWithCountryIdRequest{ provincesResp, err := this.RPC().RegionProvinceRPC().FindAllRegionProvincesWithRegionCountryId(this.AdminContext(), &pb.FindAllRegionProvincesWithRegionCountryIdRequest{
RegionCountryId: int64(ChinaCountryId), RegionCountryId: int64(ChinaCountryId),
}) })
if err != nil { if err != nil {
@@ -57,7 +57,7 @@ func (this *ProvincesAction) RunGet(params struct {
for _, province := range provincesResp.RegionProvinces { for _, province := range provincesResp.RegionProvinces {
provinceMaps = append(provinceMaps, maps.Map{ provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id, "id": province.Id,
"name": province.Name, "name": province.DisplayName,
"isChecked": lists.ContainsInt64(selectedProvinceIds, province.Id), "isChecked": lists.ContainsInt64(selectedProvinceIds, province.Id),
}) })
} }

View File

@@ -217,11 +217,8 @@ func (this *IndexAction) RunGet(params struct {
// 统计数据 // 统计数据
var bandwidth = "" var bandwidth = ""
if server.LatestServerDailyStat != nil { if server.BandwidthBytes > 0 {
var bytesPerSecond = server.LatestServerDailyStat.Bytes / 300 bandwidth = numberutils.FormatBytes(server.BandwidthBytes)
if bytesPerSecond > 0 {
bandwidth = numberutils.FormatBytes(bytesPerSecond)
}
} }
serverMaps = append(serverMaps, maps.Map{ serverMaps = append(serverMaps, maps.Map{

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"strings"
"time" "time"
) )
@@ -30,7 +31,14 @@ func (this *IndexAction) RunGet(params struct {
return return
} }
if regionResp.IpRegion != nil { if regionResp.IpRegion != nil {
this.Data["regions"] = regionResp.IpRegion.Summary var regionName = regionResp.IpRegion.Summary
// remove isp from regionName
var index = strings.LastIndex(regionName, "|")
if index > 0 {
regionName = regionName[:index]
}
this.Data["regions"] = regionName
} else { } else {
this.Data["regions"] = "" this.Data["regions"] = ""
} }

View File

@@ -4,6 +4,7 @@ package iplists
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
) )
@@ -23,5 +24,8 @@ func (this *DeleteAction) RunPost(params struct {
return return
} }
// 通知左侧菜单Badge更新
helpers.NotifyIPItemsCountChanges()
this.Success() this.Success()
} }

View File

@@ -3,6 +3,7 @@ package iplists
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs" "github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
) )
@@ -22,5 +23,8 @@ func (this *DeleteIPAction) RunPost(params struct {
return return
} }
// 通知左侧菜单Badge更新
helpers.NotifyIPItemsCountChanges()
this.Success() this.Success()
} }

View File

@@ -4,6 +4,7 @@ package iplists
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"strings" "strings"
@@ -33,5 +34,8 @@ func (this *DeleteItemsAction) RunPost(params struct {
return return
} }
// 通知左侧菜单Badge更新
helpers.NotifyIPItemsCountChanges()
this.Success() this.Success()
} }

View File

@@ -8,6 +8,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"strings"
"time" "time"
) )
@@ -165,6 +166,13 @@ func (this *IndexAction) RunGet(params struct {
var ipRegion = regionResp.IpRegion var ipRegion = regionResp.IpRegion
if ipRegion != nil { if ipRegion != nil {
region = ipRegion.Summary region = ipRegion.Summary
// remove isp from regionName
var index = strings.LastIndex(region, "|")
if index > 0 {
region = region[:index]
}
isp = ipRegion.Isp isp = ipRegion.Isp
} }
} }

View File

@@ -8,6 +8,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"strings"
"time" "time"
) )
@@ -116,6 +117,13 @@ func (this *ItemsAction) RunGet(params struct {
var ipRegion = regionResp.IpRegion var ipRegion = regionResp.IpRegion
if ipRegion != nil { if ipRegion != nil {
region = ipRegion.Summary region = ipRegion.Summary
// remove isp from regionName
var index = strings.LastIndex(region, "|")
if index > 0 {
region = region[:index]
}
isp = ipRegion.Isp isp = ipRegion.Isp
} }
} }

View File

@@ -4,6 +4,7 @@ package iplists
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
) )
@@ -20,5 +21,8 @@ func (this *ReadAllAction) RunPost(params struct{}) {
return return
} }
// 通知左侧菜单Badge更新
helpers.NotifyIPItemsCountChanges()
this.Success() this.Success()
} }

View File

@@ -0,0 +1,67 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package logs
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
timeutil "github.com/iwind/TeaGo/utils/time"
"regexp"
"strings"
)
// HasLogsAction 检查某个分区是否有日志
type HasLogsAction struct {
actionutils.ParentAction
}
func (this *HasLogsAction) RunPost(params struct {
ClusterId int64
NodeId int64
Day string
Hour string
Keyword string
Ip string
Domain string
HasError int
HasWAF int
Partition int32 `default:"-1"`
RequestId string
ServerId int64
}) {
if len(params.Day) == 0 {
params.Day = timeutil.Format("Y-m-d")
}
var day = params.Day
if len(day) > 0 && regexp.MustCompile(`\d{4}-\d{2}-\d{2}`).MatchString(day) {
day = strings.ReplaceAll(day, "-", "")
}
resp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{
Partition: params.Partition,
RequestId: params.RequestId,
NodeClusterId: params.ClusterId,
NodeId: params.NodeId,
ServerId: params.ServerId,
HasError: params.HasError > 0,
HasFirewallPolicy: params.HasWAF > 0,
Day: day,
HourFrom: params.Hour,
HourTo: params.Hour,
Keyword: params.Keyword,
Ip: params.Ip,
Domain: params.Domain,
Size: 1,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["hasLogs"] = len(resp.HttpAccessLogs) > 0
this.Success()
}

View File

@@ -18,6 +18,7 @@ func init() {
Get("", new(IndexAction)). Get("", new(IndexAction)).
GetPost("/settings", new(SettingsAction)). GetPost("/settings", new(SettingsAction)).
Post("/partitionData", new(PartitionDataAction)). Post("/partitionData", new(PartitionDataAction)).
Post("/hasLogs", new(HasLogsAction)).
EndAll() EndAll()
}) })
} }

View File

@@ -93,8 +93,16 @@ func (this *ViewPopupAction) RunGet(params struct {
} }
region := regionResp.IpRegion region := regionResp.IpRegion
if region != nil { if region != nil {
var regionName = region.Summary
// remove isp from regionName
var index = strings.LastIndex(regionName, "|")
if index > 0 {
regionName = regionName[:index]
}
regionMap = maps.Map{ regionMap = maps.Map{
"full": region.Summary, "full": regionName,
"isp": region.Isp, "isp": region.Isp,
} }
} }

View File

@@ -1,13 +1,16 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus
package access package access
import ( import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"strings"
) )
type CreatePopupAction struct { type CreatePopupAction struct {
@@ -37,6 +40,9 @@ func (this *CreatePopupAction) RunPost(params struct {
SubRequestMethod string SubRequestMethod string
SubRequestFollowRequest bool SubRequestFollowRequest bool
Exts []string
DomainsJSON []byte
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
}) { }) {
@@ -44,14 +50,42 @@ func (this *CreatePopupAction) RunPost(params struct {
Field("name", params.Name). Field("name", params.Name).
Require("请输入名称"). Require("请输入名称").
Field("type", params.Type). Field("type", params.Type).
Require("请输入认证类型") Require("请输入鉴权类型")
var ref = &serverconfigs.HTTPAuthPolicyRef{IsOn: true} var ref = &serverconfigs.HTTPAuthPolicyRef{IsOn: true}
var paramsJSON []byte
// 扩展名
var exts = utils.NewStringsStream(params.Exts).
Map(strings.TrimSpace, strings.ToLower).
Filter(utils.FilterNotEmpty).
Map(utils.MapAddPrefixFunc(".")).
Unique().
Result()
// 域名
var domains = []string{}
if len(params.DomainsJSON) > 0 {
var rawDomains = []string{}
err := json.Unmarshal(params.DomainsJSON, &rawDomains)
if err != nil {
this.ErrorPage(err)
return
}
// TODO 如果用户填写了一个网址,应该分析域名并填入
domains = utils.NewStringsStream(rawDomains).
Map(strings.TrimSpace, strings.ToLower).
Filter(utils.FilterNotEmpty).
Unique().
Result()
}
var method serverconfigs.HTTPAuthMethodInterface
switch params.Type { switch params.Type {
case serverconfigs.HTTPAuthTypeBasicAuth: case serverconfigs.HTTPAuthTypeBasicAuth:
users := []*serverconfigs.HTTPAuthBasicMethodUser{} var users = []*serverconfigs.HTTPAuthBasicMethodUser{}
err := json.Unmarshal(params.HttpAuthBasicAuthUsersJSON, &users) err := json.Unmarshal(params.HttpAuthBasicAuthUsersJSON, &users)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
@@ -60,40 +94,39 @@ func (this *CreatePopupAction) RunPost(params struct {
if len(users) == 0 { if len(users) == 0 {
this.Fail("请添加至少一个用户") this.Fail("请添加至少一个用户")
} }
method := &serverconfigs.HTTPAuthBasicMethod{ method = &serverconfigs.HTTPAuthBasicMethod{
Users: users, Users: users,
Realm: params.BasicAuthRealm, Realm: params.BasicAuthRealm,
Charset: params.BasicAuthCharset, Charset: params.BasicAuthCharset,
} }
methodJSON, err := json.Marshal(method)
if err != nil {
this.ErrorPage(err)
return
}
paramsJSON = methodJSON
case serverconfigs.HTTPAuthTypeSubRequest: case serverconfigs.HTTPAuthTypeSubRequest:
params.Must.Field("subRequestURL", params.SubRequestURL). params.Must.Field("subRequestURL", params.SubRequestURL).
Require("请输入子请求URL") Require("请输入子请求URL")
if params.SubRequestFollowRequest { if params.SubRequestFollowRequest {
params.SubRequestMethod = "" params.SubRequestMethod = ""
} }
method := &serverconfigs.HTTPAuthSubRequestMethod{ method = &serverconfigs.HTTPAuthSubRequestMethod{
URL: params.SubRequestURL, URL: params.SubRequestURL,
Method: params.SubRequestMethod, Method: params.SubRequestMethod,
} }
methodJSON, err := json.Marshal(method)
if err != nil {
this.ErrorPage(err)
return
}
paramsJSON = methodJSON
default: default:
this.Fail("不支持的认证类型'" + params.Type + "'") this.Fail("不支持的鉴权类型'" + params.Type + "'")
}
if method == nil {
this.Fail("无法找到对应的鉴权方式")
}
method.SetExts(exts)
method.SetDomains(domains)
paramsJSON, err := json.Marshal(method)
if err != nil {
this.ErrorPage(err)
return
} }
var paramsMap map[string]interface{} var paramsMap map[string]interface{}
err := json.Unmarshal(paramsJSON, &paramsMap) err = json.Unmarshal(paramsJSON, &paramsMap)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
@@ -108,7 +141,7 @@ func (this *CreatePopupAction) RunPost(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
defer this.CreateLogInfo("创建HTTP认证 %d", createResp.HttpAuthPolicyId) defer this.CreateLogInfo("创建HTTP鉴权 %d", createResp.HttpAuthPolicyId)
ref.AuthPolicyId = createResp.HttpAuthPolicyId ref.AuthPolicyId = createResp.HttpAuthPolicyId
ref.AuthPolicy = &serverconfigs.HTTPAuthPolicy{ ref.AuthPolicy = &serverconfigs.HTTPAuthPolicy{
Id: createResp.HttpAuthPolicyId, Id: createResp.HttpAuthPolicyId,

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
) )
type IndexAction struct { type IndexAction struct {
@@ -28,6 +29,28 @@ func (this *IndexAction) RunGet(params struct {
} }
this.Data["webId"] = webConfig.Id this.Data["webId"] = webConfig.Id
// 移除不存在的鉴权方法
var allTypes = []string{}
for _, def := range serverconfigs.FindAllHTTPAuthTypes() {
allTypes = append(allTypes, def.Code)
}
if webConfig.Auth != nil {
var refs = webConfig.Auth.PolicyRefs
var realRefs = []*serverconfigs.HTTPAuthPolicyRef{}
for _, ref := range refs {
if ref.AuthPolicy == nil {
continue
}
if !lists.ContainsString(allTypes, ref.AuthPolicy.Type) {
continue
}
realRefs = append(realRefs, ref)
}
webConfig.Auth.PolicyRefs = realRefs
}
this.Data["authConfig"] = webConfig.Auth this.Data["authConfig"] = webConfig.Auth
this.Show() this.Show()
@@ -38,9 +61,8 @@ func (this *IndexAction) RunPost(params struct {
AuthJSON []byte AuthJSON []byte
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF
}) { }) {
defer this.CreateLogInfo("修改Web %d 的认证设置", params.WebId) defer this.CreateLogInfo("修改Web %d 的鉴权设置", params.WebId)
var authConfig = &serverconfigs.HTTPAuthConfig{} var authConfig = &serverconfigs.HTTPAuthConfig{}
err := json.Unmarshal(params.AuthJSON, authConfig) err := json.Unmarshal(params.AuthJSON, authConfig)
@@ -53,7 +75,7 @@ func (this *IndexAction) RunPost(params struct {
this.Fail("配置校验失败:" + err.Error()) this.Fail("配置校验失败:" + err.Error())
} }
// 保存之前删除多的配置信息 // 保存之前删除多的配置信息
for _, ref := range authConfig.PolicyRefs { for _, ref := range authConfig.PolicyRefs {
ref.AuthPolicy = nil ref.AuthPolicy = nil
} }

View File

@@ -16,6 +16,7 @@ func init() {
GetPost("", new(IndexAction)). GetPost("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)). GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/updatePopup", new(UpdatePopupAction)). GetPost("/updatePopup", new(UpdatePopupAction)).
Post("/random", new(RandomAction)).
EndAll() EndAll()
}) })
} }

View File

@@ -0,0 +1,18 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package access
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/rands"
)
type RandomAction struct {
actionutils.ParentAction
}
func (this *RandomAction) RunPost(params struct{}) {
this.Data["random"] = rands.HexString(32)
this.Success()
}

View File

@@ -1,14 +1,17 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus
package access package access
import ( import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"strings"
) )
type UpdatePopupAction struct { type UpdatePopupAction struct {
@@ -70,17 +73,20 @@ func (this *UpdatePopupAction) RunPost(params struct {
SubRequestMethod string SubRequestMethod string
SubRequestFollowRequest bool SubRequestFollowRequest bool
Exts []string
DomainsJSON []byte
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
}) { }) {
defer this.CreateLogInfo("修改HTTP认证 %d", params.PolicyId) defer this.CreateLogInfo("修改HTTP鉴权 %d", params.PolicyId)
policyResp, err := this.RPC().HTTPAuthPolicyRPC().FindEnabledHTTPAuthPolicy(this.AdminContext(), &pb.FindEnabledHTTPAuthPolicyRequest{HttpAuthPolicyId: params.PolicyId}) policyResp, err := this.RPC().HTTPAuthPolicyRPC().FindEnabledHTTPAuthPolicy(this.AdminContext(), &pb.FindEnabledHTTPAuthPolicyRequest{HttpAuthPolicyId: params.PolicyId})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
policy := policyResp.HttpAuthPolicy var policy = policyResp.HttpAuthPolicy
if policy == nil { if policy == nil {
this.NotFound("httpAuthPolicy", params.PolicyId) this.NotFound("httpAuthPolicy", params.PolicyId)
return return
@@ -91,12 +97,40 @@ func (this *UpdatePopupAction) RunPost(params struct {
Field("name", params.Name). Field("name", params.Name).
Require("请输入名称") Require("请输入名称")
// 扩展名
var exts = utils.NewStringsStream(params.Exts).
Map(strings.TrimSpace, strings.ToLower).
Filter(utils.FilterNotEmpty).
Map(utils.MapAddPrefixFunc(".")).
Unique().
Result()
// 域名
var domains = []string{}
if len(params.DomainsJSON) > 0 {
var rawDomains = []string{}
err := json.Unmarshal(params.DomainsJSON, &rawDomains)
if err != nil {
this.ErrorPage(err)
return
}
// TODO 如果用户填写了一个网址,应该分析域名并填入
domains = utils.NewStringsStream(rawDomains).
Map(strings.TrimSpace, strings.ToLower).
Filter(utils.FilterNotEmpty).
Unique().
Result()
}
var ref = &serverconfigs.HTTPAuthPolicyRef{IsOn: true} var ref = &serverconfigs.HTTPAuthPolicyRef{IsOn: true}
var paramsJSON []byte
var method serverconfigs.HTTPAuthMethodInterface
switch policyType { switch policyType {
case serverconfigs.HTTPAuthTypeBasicAuth: case serverconfigs.HTTPAuthTypeBasicAuth:
users := []*serverconfigs.HTTPAuthBasicMethodUser{} var users = []*serverconfigs.HTTPAuthBasicMethodUser{}
err := json.Unmarshal(params.HttpAuthBasicAuthUsersJSON, &users) err := json.Unmarshal(params.HttpAuthBasicAuthUsersJSON, &users)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
@@ -105,36 +139,35 @@ func (this *UpdatePopupAction) RunPost(params struct {
if len(users) == 0 { if len(users) == 0 {
this.Fail("请添加至少一个用户") this.Fail("请添加至少一个用户")
} }
method := &serverconfigs.HTTPAuthBasicMethod{ method = &serverconfigs.HTTPAuthBasicMethod{
Users: users, Users: users,
Realm: params.BasicAuthRealm, Realm: params.BasicAuthRealm,
Charset: params.BasicAuthCharset, Charset: params.BasicAuthCharset,
} }
methodJSON, err := json.Marshal(method)
if err != nil {
this.ErrorPage(err)
return
}
paramsJSON = methodJSON
case serverconfigs.HTTPAuthTypeSubRequest: case serverconfigs.HTTPAuthTypeSubRequest:
params.Must.Field("subRequestURL", params.SubRequestURL). params.Must.Field("subRequestURL", params.SubRequestURL).
Require("请输入子请求URL") Require("请输入子请求URL")
if params.SubRequestFollowRequest { if params.SubRequestFollowRequest {
params.SubRequestMethod = "" params.SubRequestMethod = ""
} }
method := &serverconfigs.HTTPAuthSubRequestMethod{ method = &serverconfigs.HTTPAuthSubRequestMethod{
URL: params.SubRequestURL, URL: params.SubRequestURL,
Method: params.SubRequestMethod, Method: params.SubRequestMethod,
} }
methodJSON, err := json.Marshal(method)
if err != nil {
this.ErrorPage(err)
return
}
paramsJSON = methodJSON
default: default:
this.Fail("不支持的认证类型'" + policyType + "'") this.Fail("不支持的鉴权类型'" + policyType + "'")
}
if method == nil {
this.Fail("无法找到对应的鉴权方式")
}
method.SetExts(exts)
method.SetDomains(domains)
paramsJSON, err := json.Marshal(method)
if err != nil {
this.ErrorPage(err)
return
} }
var paramsMap map[string]interface{} var paramsMap map[string]interface{}

View File

@@ -5,6 +5,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils" "github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
) )
@@ -27,19 +28,45 @@ func (this *CreatePopupAction) RunGet(params struct {
func (this *CreatePopupAction) RunPost(params struct { func (this *CreatePopupAction) RunPost(params struct {
CacheRefJSON []byte CacheRefJSON []byte
CondType string
CondJSON []byte
CondIsCaseInsensitive bool
Must *actions.Must Must *actions.Must
}) { }) {
var cacheRef = &serverconfigs.HTTPCacheRef{} var cacheRef = &serverconfigs.HTTPCacheRef{}
err := json.Unmarshal(params.CacheRefJSON, cacheRef) err := json.Unmarshal(params.CacheRefJSON, cacheRef)
if err != nil { if err != nil {
this.ErrorPage(err) this.Fail("解析条件出错:" + err.Error() + ", JSON: " + string(params.CacheRefJSON))
return return
} }
if len(params.CondJSON) > 0 {
var cond = &shared.HTTPRequestCond{}
err = json.Unmarshal(params.CondJSON, cond)
if err != nil {
this.Fail("解析条件出错:" + err.Error() + ", JSON: " + string(params.CondJSON))
return
}
cond.Type = params.CondType
cond.IsCaseInsensitive = params.CondIsCaseInsensitive
cacheRef.SimpleCond = cond
// 将组合条件置为空
cacheRef.Conds = &shared.HTTPRequestCondsConfig{}
}
err = cacheRef.Init()
if err != nil {
this.Fail("解析条件出错:" + err.Error())
return
}
if len(cacheRef.Key) == 0 { if len(cacheRef.Key) == 0 {
this.Fail("请输入缓存Key") this.Fail("请输入缓存Key")
} }
if cacheRef.Conds == nil || len(cacheRef.Conds.Groups) == 0 { if (cacheRef.Conds == nil || len(cacheRef.Conds.Groups) == 0) && cacheRef.SimpleCond == nil {
this.Fail("请填写匹配条件分组") this.Fail("请填写匹配条件分组")
} }

View File

@@ -15,6 +15,7 @@ func init() {
Prefix("/servers/server/settings/dns"). Prefix("/servers/server/settings/dns").
GetPost("", new(IndexAction)). GetPost("", new(IndexAction)).
Post("/regenerateCNAME", new(RegenerateCNAMEAction)). Post("/regenerateCNAME", new(RegenerateCNAMEAction)).
GetPost("/updateCNAMEPopup", new(UpdateCNAMEPopupAction)).
EndAll() EndAll()
}) })
} }

View File

@@ -16,7 +16,7 @@ func (this *RegenerateCNAMEAction) RunPost(params struct {
}) { }) {
defer this.CreateLogInfo("重新生成服务 %d 的CNAME", params.ServerId) defer this.CreateLogInfo("重新生成服务 %d 的CNAME", params.ServerId)
_, err := this.RPC().ServerRPC().RegenerateServerCNAME(this.AdminContext(), &pb.RegenerateServerCNAMERequest{ServerId: params.ServerId}) _, err := this.RPC().ServerRPC().RegenerateServerDNSName(this.AdminContext(), &pb.RegenerateServerDNSNameRequest{ServerId: params.ServerId})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return

View File

@@ -0,0 +1,94 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package dns
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/types"
"regexp"
"strings"
)
type UpdateCNAMEPopupAction struct {
actionutils.ParentAction
}
func (this *UpdateCNAMEPopupAction) RunGet(params struct {
ServerId int64
}) {
this.Data["serverId"] = params.ServerId
dnsInfoResp, err := this.RPC().ServerRPC().FindEnabledServerDNS(this.AdminContext(), &pb.FindEnabledServerDNSRequest{ServerId: params.ServerId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["dnsName"] = dnsInfoResp.DnsName
this.Show()
}
func (this *UpdateCNAMEPopupAction) RunPost(params struct {
ServerId int64
DnsName string
}) {
defer this.CreateLogInfo("修改服务 %d CNAME为 %s", params.ServerId, params.DnsName)
var dnsName = strings.ToLower(params.DnsName)
if len(dnsName) == 0 {
this.FailField("dnsName", "CNAME不能为空")
}
const maxLen = 30
if len(dnsName) > maxLen {
this.FailField("dnsName", "CNAME长度不能超过"+types.String(maxLen)+"个字符")
}
if !regexp.MustCompile(`^[a-z0-9]{1,` + types.String(maxLen) + `}$`).MatchString(dnsName) {
this.FailField("dnsName", "CNAME中只能包含数字、英文字母")
}
serverResp, err := this.RPC().ServerRPC().FindEnabledServer(this.AdminContext(), &pb.FindEnabledServerRequest{ServerId: params.ServerId})
if err != nil {
this.ErrorPage(err)
return
}
var server = serverResp.Server
if server == nil {
this.Fail("找不到要修改的服务")
}
if server.NodeCluster == nil {
this.Fail("服务必须先分配到一个集群才能修改")
}
var clusterId = server.NodeCluster.Id
if server.DnsName == params.DnsName {
// 没有修改则直接返回
this.Success()
}
serverIdResp, err := this.RPC().ServerRPC().FindServerIdWithDNSName(this.AdminContext(), &pb.FindServerIdWithDNSNameRequest{
NodeClusterId: clusterId,
DnsName: dnsName,
})
if err != nil {
this.ErrorPage(err)
return
}
if serverIdResp.ServerId > 0 && serverIdResp.ServerId != params.ServerId {
this.FailField("dnsName", "当前CNAME已被别的服务占用请换一个")
}
_, err = this.RPC().ServerRPC().UpdateServerDNSName(this.AdminContext(), &pb.UpdateServerDNSNameRequest{
ServerId: params.ServerId,
DnsName: dnsName,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
) )
type IndexAction struct { type IndexAction struct {
@@ -28,6 +29,28 @@ func (this *IndexAction) RunGet(params struct {
} }
this.Data["webId"] = webConfig.Id this.Data["webId"] = webConfig.Id
// 移除不存在的鉴权方法
var allTypes = []string{}
for _, def := range serverconfigs.FindAllHTTPAuthTypes() {
allTypes = append(allTypes, def.Code)
}
if webConfig.Auth != nil {
var refs = webConfig.Auth.PolicyRefs
var realRefs = []*serverconfigs.HTTPAuthPolicyRef{}
for _, ref := range refs {
if ref.AuthPolicy == nil {
continue
}
if !lists.ContainsString(allTypes, ref.AuthPolicy.Type) {
continue
}
realRefs = append(realRefs, ref)
}
webConfig.Auth.PolicyRefs = realRefs
}
this.Data["authConfig"] = webConfig.Auth this.Data["authConfig"] = webConfig.Auth
this.Show() this.Show()
@@ -38,7 +61,6 @@ func (this *IndexAction) RunPost(params struct {
AuthJSON []byte AuthJSON []byte
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF
}) { }) {
defer this.CreateLogInfo("修改Web %d 的认证设置", params.WebId) defer this.CreateLogInfo("修改Web %d 的认证设置", params.WebId)

View File

@@ -98,7 +98,7 @@ func (this *LocationHelper) createMenus(serverIdString string, locationIdString
"isOn": locationConfig != nil && locationConfig.Web != nil && locationConfig.Web.Cache != nil && locationConfig.Web.Cache.IsPrior && locationConfig.Web.Cache.IsOn, "isOn": locationConfig != nil && locationConfig.Web != nil && locationConfig.Web.Cache != nil && locationConfig.Web.Cache.IsPrior && locationConfig.Web.Cache.IsOn,
}) })
menuItems = append(menuItems, maps.Map{ menuItems = append(menuItems, maps.Map{
"name": "访问控制", "name": "访问鉴权",
"url": "/servers/server/settings/locations/access?serverId=" + serverIdString + "&locationId=" + locationIdString, "url": "/servers/server/settings/locations/access?serverId=" + serverIdString + "&locationId=" + locationIdString,
"isActive": secondMenuItem == "access", "isActive": secondMenuItem == "access",
"isOn": locationConfig != nil && locationConfig.Web != nil && locationConfig.Web.Auth != nil && locationConfig.Web.Auth.IsPrior, "isOn": locationConfig != nil && locationConfig.Web != nil && locationConfig.Web.Auth != nil && locationConfig.Web.Auth.IsPrior,

View File

@@ -55,7 +55,7 @@ func (this *CountriesAction) RunGet(params struct {
for _, country := range countriesResp.RegionCountries { for _, country := range countriesResp.RegionCountries {
countryMaps = append(countryMaps, maps.Map{ countryMaps = append(countryMaps, maps.Map{
"id": country.Id, "id": country.Id,
"name": country.Name, "name": country.DisplayName,
"letter": strings.ToUpper(string(country.Pinyin[0][0])), "letter": strings.ToUpper(string(country.Pinyin[0][0])),
"isChecked": lists.ContainsInt64(selectedCountryIds, country.Id), "isChecked": lists.ContainsInt64(selectedCountryIds, country.Id),
}) })

View File

@@ -57,7 +57,7 @@ func (this *ProvincesAction) RunGet(params struct {
for _, province := range provincesResp.RegionProvinces { for _, province := range provincesResp.RegionProvinces {
provinceMaps = append(provinceMaps, maps.Map{ provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id, "id": province.Id,
"name": province.Name, "name": province.DisplayName,
"isChecked": lists.ContainsInt64(selectedProvinceIds, province.Id), "isChecked": lists.ContainsInt64(selectedProvinceIds, province.Id),
}) })
} }

View File

@@ -304,7 +304,7 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
"isOn": serverConfig.Web != nil && serverConfig.Web.Cache != nil && serverConfig.Web.Cache.IsOn, "isOn": serverConfig.Web != nil && serverConfig.Web.Cache != nil && serverConfig.Web.Cache.IsOn,
}) })
menuItems = append(menuItems, maps.Map{ menuItems = append(menuItems, maps.Map{
"name": "访问控制", "name": "访问鉴权",
"url": "/servers/server/settings/access?serverId=" + serverIdString, "url": "/servers/server/settings/access?serverId=" + serverIdString,
"isActive": secondMenuItem == "access", "isActive": secondMenuItem == "access",
"isOn": serverConfig.Web != nil && serverConfig.Web.Auth != nil && serverConfig.Web.Auth.IsOn, "isOn": serverConfig.Web != nil && serverConfig.Web.Auth != nil && serverConfig.Web.Auth.IsOn,

View File

@@ -40,7 +40,7 @@ func (this *IndexAction) RunGet(params struct{}) {
if country != nil { if country != nil {
countryMaps = append(countryMaps, maps.Map{ countryMaps = append(countryMaps, maps.Map{
"id": country.Id, "id": country.Id,
"name": country.Name, "name": country.DisplayName,
}) })
} }
} }
@@ -58,7 +58,7 @@ func (this *IndexAction) RunGet(params struct{}) {
if province != nil { if province != nil {
provinceMaps = append(provinceMaps, maps.Map{ provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id, "id": province.Id,
"name": province.Name, "name": province.DisplayName,
}) })
} }
} }

View File

@@ -25,7 +25,7 @@ func (this *ProvinceOptionsAction) RunPost(params struct{}) {
} }
provinceMaps = append(provinceMaps, maps.Map{ provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id, "id": province.Id,
"name": province.Name, "name": province.DisplayName,
"codes": province.Codes, "codes": province.Codes,
}) })
} }

View File

@@ -32,7 +32,7 @@ func (this *SelectCountriesPopupAction) RunGet(params struct {
for _, country := range countriesResp.RegionCountries { for _, country := range countriesResp.RegionCountries {
countryMaps = append(countryMaps, maps.Map{ countryMaps = append(countryMaps, maps.Map{
"id": country.Id, "id": country.Id,
"name": country.Name, "name": country.DisplayName,
"letter": strings.ToUpper(string(country.Pinyin[0][0])), "letter": strings.ToUpper(string(country.Pinyin[0][0])),
"isChecked": lists.ContainsInt64(selectedCountryIds, country.Id), "isChecked": lists.ContainsInt64(selectedCountryIds, country.Id),
}) })
@@ -61,7 +61,7 @@ func (this *SelectCountriesPopupAction) RunPost(params struct {
} }
countryMaps = append(countryMaps, maps.Map{ countryMaps = append(countryMaps, maps.Map{
"id": country.Id, "id": country.Id,
"name": country.Name, "name": country.DisplayName,
}) })
} }
this.Data["countries"] = countryMaps this.Data["countries"] = countryMaps

View File

@@ -33,7 +33,7 @@ func (this *SelectProvincesPopupAction) RunGet(params struct {
for _, province := range provincesResp.RegionProvinces { for _, province := range provincesResp.RegionProvinces {
provinceMaps = append(provinceMaps, maps.Map{ provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id, "id": province.Id,
"name": province.Name, "name": province.DisplayName,
"isChecked": lists.ContainsInt64(selectedProvinceIds, province.Id), "isChecked": lists.ContainsInt64(selectedProvinceIds, province.Id),
}) })
} }
@@ -61,7 +61,7 @@ func (this *SelectProvincesPopupAction) RunPost(params struct {
} }
provinceMaps = append(provinceMaps, maps.Map{ provinceMaps = append(provinceMaps, maps.Map{
"id": province.Id, "id": province.Id,
"name": province.Name, "name": province.DisplayName,
}) })
} }
this.Data["provinces"] = provinceMaps this.Data["provinces"] = provinceMaps

View File

@@ -1,9 +1,13 @@
package users package users
import ( import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils" "github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/xlzd/gotp" "github.com/xlzd/gotp"
@@ -22,15 +26,16 @@ func (this *CreatePopupAction) RunGet(params struct{}) {
} }
func (this *CreatePopupAction) RunPost(params struct { func (this *CreatePopupAction) RunPost(params struct {
Username string Username string
Pass1 string Pass1 string
Pass2 string Pass2 string
Fullname string Fullname string
Mobile string Mobile string
Tel string Tel string
Email string Email string
Remark string Remark string
ClusterId int64 ClusterId int64
FeaturesType string
// OTP // OTP
OtpOn bool OtpOn bool
@@ -38,6 +43,12 @@ func (this *CreatePopupAction) RunPost(params struct {
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
}) { }) {
var userId int64
defer func() {
this.CreateLogInfo("创建用户 %d", userId)
}()
params.Must. params.Must.
Field("username", params.Username). Field("username", params.Username).
Require("请输入用户名"). Require("请输入用户名").
@@ -97,9 +108,53 @@ func (this *CreatePopupAction) RunPost(params struct {
return return
} }
var userId = createResp.UserId userId = createResp.UserId
defer this.CreateLogInfo("创建用户 %d", userId) // 功能
if teaconst.IsPlus {
if params.FeaturesType == "default" {
resp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeUserRegisterConfig})
if err != nil {
this.ErrorPage(err)
return
}
var config = userconfigs.DefaultUserRegisterConfig()
if len(resp.ValueJSON) > 0 {
err = json.Unmarshal(resp.ValueJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().UserRPC().UpdateUserFeatures(this.AdminContext(), &pb.UpdateUserFeaturesRequest{
UserId: userId,
FeatureCodes: config.Features,
})
if err != nil {
this.ErrorPage(err)
return
}
}
} else if params.FeaturesType == "all" {
featuresResp, err := this.RPC().UserRPC().FindAllUserFeatureDefinitions(this.AdminContext(), &pb.FindAllUserFeatureDefinitionsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var featureCodes = []string{}
for _, def := range featuresResp.Features {
featureCodes = append(featureCodes, def.Code)
}
_, err = this.RPC().UserRPC().UpdateUserFeatures(this.AdminContext(), &pb.UpdateUserFeaturesRequest{
UserId: userId,
FeatureCodes: featureCodes,
})
if err != nil {
this.ErrorPage(err)
return
}
}
}
// OTP // OTP
if params.OtpOn { if params.OtpOn {

View File

@@ -15,6 +15,8 @@ func init() {
Prefix("/users"). Prefix("/users").
Get("", new(IndexAction)). Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)). GetPost("/createPopup", new(CreatePopupAction)).
// 单个用户信息
Get("/user", new(UserAction)). Get("/user", new(UserAction)).
GetPost("/update", new(UpdateAction)). GetPost("/update", new(UpdateAction)).
Post("/delete", new(DeleteAction)). Post("/delete", new(DeleteAction)).
@@ -28,6 +30,8 @@ func init() {
GetPost("/createPopup", new(accesskeys.CreatePopupAction)). GetPost("/createPopup", new(accesskeys.CreatePopupAction)).
Post("/delete", new(accesskeys.DeleteAction)). Post("/delete", new(accesskeys.DeleteAction)).
Post("/updateIsOn", new(accesskeys.UpdateIsOnAction)). Post("/updateIsOn", new(accesskeys.UpdateIsOnAction)).
//
EndAll() EndAll()
}) })
} }

View File

@@ -0,0 +1 @@
servers.go

View File

@@ -22,6 +22,22 @@ import (
var nodeLogsCountChanges = make(chan bool, 1) var nodeLogsCountChanges = make(chan bool, 1)
var ipItemsCountChanges = make(chan bool, 1) var ipItemsCountChanges = make(chan bool, 1)
func NotifyNodeLogsCountChange() {
select {
case nodeLogsCountChanges <- true:
default:
}
}
func NotifyIPItemsCountChanges() {
select {
case ipItemsCountChanges <- true:
default:
}
}
// 运行日志 // 运行日志
var countUnreadNodeLogs int64 = 0 var countUnreadNodeLogs int64 = 0
var nodeLogsType = "" var nodeLogsType = ""

View File

@@ -10,6 +10,7 @@ import (
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"net" "net"
"net/http" "net/http"
"net/url"
"regexp" "regexp"
"sync" "sync"
) )
@@ -120,15 +121,20 @@ func checkRequestSecurity(securityConfig *systemconfigs.SecurityConfig, req *htt
} }
var userAgent = req.UserAgent() var userAgent = req.UserAgent()
var referer = req.Referer() var refererURL = req.Referer()
var referHost = ""
u, err := url.Parse(refererURL)
if err == nil {
referHost = u.Host
}
// 检查搜索引擎 // 检查搜索引擎
if securityConfig.DenySearchEngines && (len(userAgent) == 0 || searchEngineRegex.MatchString(userAgent) || (len(referer) > 0 && searchEngineRegex.MatchString(referer))) { if securityConfig.DenySearchEngines && (len(userAgent) == 0 || searchEngineRegex.MatchString(userAgent) || (len(referHost) > 0 && searchEngineRegex.MatchString(referHost))) {
return false return false
} }
// 检查爬虫 // 检查爬虫
if securityConfig.DenySpiders && (len(userAgent) == 0 || spiderRegexp.MatchString(userAgent) || (len(referer) > 0 && spiderRegexp.MatchString(referer))) { if securityConfig.DenySpiders && (len(userAgent) == 0 || spiderRegexp.MatchString(userAgent) || (len(referHost) > 0 && spiderRegexp.MatchString(referHost))) {
return false return false
} }

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,9 @@ Vue.component("node-ddos-protection-config-box", {
maxConnections: 0, maxConnections: 0,
maxConnectionsPerIP: 0, maxConnectionsPerIP: 0,
newConnectionsRate: 0, newConnectionsRate: 0,
newConnectionsRateBlockTimeout: 0,
newConnectionsSecondlyRate: 0,
newConnectionSecondlyRateBlockTimeout: 0,
allowIPList: [], allowIPList: [],
ports: [] ports: []
} }
@@ -24,6 +27,9 @@ Vue.component("node-ddos-protection-config-box", {
maxConnections: 0, maxConnections: 0,
maxConnectionsPerIP: 0, maxConnectionsPerIP: 0,
newConnectionsRate: 0, newConnectionsRate: 0,
newConnectionsRateBlockTimeout: 0,
newConnectionsSecondlyRate: 0,
newConnectionSecondlyRateBlockTimeout: 0,
allowIPList: [], allowIPList: [],
ports: [] ports: []
} }
@@ -80,13 +86,51 @@ Vue.component("node-ddos-protection-config-box", {
</td> </td>
</tr> </tr>
<tr> <tr>
<td>单IP TCP新连接速率</td> <td>单IP TCP新连接速率<em>(分钟)</em></td>
<td> <td>
<div class="ui input right labeled"> <div class="ui fields inline">
<digit-input name="tcpNewConnectionsRate" v-model="config.tcp.newConnectionsRate" maxlength="6" size="6" style="width: 6em" :min="defaultConfigs.tcpNewConnectionsMinRate"></digit-input> <div class="ui field">
<span class="ui label">个新连接/每分钟</span> <div class="ui input right labeled">
<digit-input name="tcpNewConnectionsRate" v-model="config.tcp.newConnectionsRate" maxlength="6" size="6" style="width: 6em" :min="defaultConfigs.tcpNewConnectionsMinRate"></digit-input>
<span class="ui label">个新连接/每分钟</span>
</div>
</div>
<div class="ui field" style="line-height: 2.4em">
屏蔽
</div>
<div class="ui field">
<div class="ui input right labeled">
<digit-input name="tcpNewConnectionsRateBlockTimeout" v-model="config.tcp.newConnectionsRateBlockTimeout" maxlength="6" size="6" style="width: 5em"></digit-input>
<span class="ui label">秒</span>
</div>
</div>
</div> </div>
<p class="comment">单个IP可以创建TCP新连接的速率。如果为0则默认为{{defaultConfigs.tcpNewConnectionsRate}};最小值为{{defaultConfigs.tcpNewConnectionsMinRate}}。</p>
<p class="comment">单个IP每分钟可以创建TCP新连接的数量。如果为0则默认为{{defaultConfigs.tcpNewConnectionsMinutelyRate}};最小值为{{defaultConfigs.tcpNewConnectionsMinMinutelyRate}}。如果没有填写屏蔽时间,则只丢弃数据包。</p>
</td>
</tr>
<tr>
<td>单IP TCP新连接速率<em>(秒钟)</em></td>
<td>
<div class="ui fields inline">
<div class="ui field">
<div class="ui input right labeled">
<digit-input name="tcpNewConnectionsSecondlyRate" v-model="config.tcp.newConnectionsSecondlyRate" maxlength="6" size="6" style="width: 6em" :min="defaultConfigs.tcpNewConnectionsMinRate"></digit-input>
<span class="ui label">个新连接/每秒钟</span>
</div>
</div>
<div class="ui field" style="line-height: 2.4em">
屏蔽
</div>
<div class="ui field">
<div class="ui input right labeled">
<digit-input name="tcpNewConnectionsSecondlyRateBlockTimeout" v-model="config.tcp.newConnectionsSecondlyRateBlockTimeout" maxlength="6" size="6" style="width: 5em"></digit-input>
<span class="ui label">秒</span>
</div>
</div>
</div>
<p class="comment">单个IP每秒钟可以创建TCP新连接的数量。如果为0则默认为{{defaultConfigs.tcpNewConnectionsSecondlyRate}};最小值为{{defaultConfigs.tcpNewConnectionsMinSecondlyRate}}。如果没有填写屏蔽时间,则只丢弃数据包。</p>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@@ -0,0 +1,3 @@
Vue.component("dot", {
template: '<span style="display: inline-block; padding-bottom: 3px"><i class="icon circle tiny"></i></span>'
})

View File

@@ -4,6 +4,7 @@ Vue.component("sort-arrow", {
data: function () { data: function () {
let url = window.location.toString() let url = window.location.toString()
let order = "" let order = ""
let iconTitle = ""
let newArgs = [] let newArgs = []
if (window.location.search != null && window.location.search.length > 0) { if (window.location.search != null && window.location.search.length > 0) {
let queryString = window.location.search.substring(1) let queryString = window.location.search.substring(1)
@@ -26,10 +27,13 @@ Vue.component("sort-arrow", {
} }
if (order == "asc") { if (order == "asc") {
newArgs.push(this.name + "=desc") newArgs.push(this.name + "=desc")
iconTitle = "当前正序排列"
} else if (order == "desc") { } else if (order == "desc") {
newArgs.push(this.name + "=asc") newArgs.push(this.name + "=asc")
iconTitle = "当前倒序排列"
} else { } else {
newArgs.push(this.name + "=desc") newArgs.push(this.name + "=desc")
iconTitle = "当前正序排列"
} }
let qIndex = url.indexOf("?") let qIndex = url.indexOf("?")
@@ -41,8 +45,9 @@ Vue.component("sort-arrow", {
return { return {
order: order, order: order,
url: url url: url,
iconTitle: iconTitle
} }
}, },
template: `<a :href="url" title="排序">&nbsp; <i class="ui icon long arrow small" :class="{down: order == 'asc', up: order == 'desc', 'down grey': order == '' || order == null}"></i></a>` template: `<a :href="url" :title="iconTitle">&nbsp; <i class="ui icon long arrow small" :class="{down: order == 'asc', up: order == 'desc', 'down grey': order == '' || order == null}"></i></a>`
}) })

View File

@@ -68,6 +68,9 @@ Vue.component("values-box", {
startEditing: function () { startEditing: function () {
this.isEditing = !this.isEditing this.isEditing = !this.isEditing
},
allValues: function () {
return this.realValues
} }
}, },
template: `<div> template: `<div>

View File

@@ -22,6 +22,11 @@ Vue.component("node-level-selector", {
levelCode: levelCode levelCode: levelCode
} }
}, },
watch: {
levelCode: function (code) {
this.$emit("change", code)
}
},
template: `<div> template: `<div>
<select class="ui dropdown auto-width" name="level" v-model="levelCode"> <select class="ui dropdown auto-width" name="level" v-model="levelCode">
<option v-for="level in levels" :value="level.code">{{level.name}}</option> <option v-for="level in levels" :value="level.code">{{level.name}}</option>

View File

@@ -0,0 +1,154 @@
Vue.component("ns-node-ddos-protection-config-box", {
props: ["v-ddos-protection-config", "v-default-configs", "v-is-node", "v-cluster-is-on"],
data: function () {
let config = this.vDdosProtectionConfig
if (config == null) {
config = {
tcp: {
isPrior: false,
isOn: false,
maxConnections: 0,
maxConnectionsPerIP: 0,
newConnectionsRate: 0,
newConnectionsRateBlockTimeout: 0,
newConnectionsSecondlyRate: 0,
newConnectionSecondlyRateBlockTimeout: 0,
allowIPList: [],
ports: []
}
}
}
// initialize
if (config.tcp == null) {
config.tcp = {
isPrior: false,
isOn: false,
maxConnections: 0,
maxConnectionsPerIP: 0,
newConnectionsRate: 0,
newConnectionsRateBlockTimeout: 0,
newConnectionsSecondlyRate: 0,
newConnectionSecondlyRateBlockTimeout: 0,
allowIPList: [],
ports: []
}
}
return {
config: config,
defaultConfigs: this.vDefaultConfigs,
isNode: this.vIsNode,
isAddingPort: false
}
},
methods: {
changeTCPPorts: function (ports) {
this.config.tcp.ports = ports
},
changeTCPAllowIPList: function (ipList) {
this.config.tcp.allowIPList = ipList
}
},
template: `<div>
<input type="hidden" name="ddosProtectionJSON" :value="JSON.stringify(config)"/>
<p class="comment">功能说明:此功能为<strong>试验性质</strong>目前仅能防御简单的DDoS攻击试验期间建议仅在被攻击时启用仅支持已安装<code-label>nftables v0.9</code-label>以上的Linux系统。<pro-warning-label></pro-warning-label></p>
<div class="ui message" v-if="vClusterIsOn">当前节点所在集群已设置DDoS防护。</div>
<h4>TCP设置</h4>
<table class="ui table definition selectable">
<prior-checkbox :v-config="config.tcp" v-if="isNode"></prior-checkbox>
<tbody v-show="config.tcp.isPrior || !isNode">
<tr>
<td class="title">启用</td>
<td>
<checkbox v-model="config.tcp.isOn"></checkbox>
</td>
</tr>
</tbody>
<tbody v-show="config.tcp.isOn && (config.tcp.isPrior || !isNode)">
<tr>
<td class="title">单节点TCP最大连接数</td>
<td>
<digit-input name="tcpMaxConnections" v-model="config.tcp.maxConnections" maxlength="6" size="6" style="width: 6em"></digit-input>
<p class="comment">单个节点可以接受的TCP最大连接数。如果为0则默认为{{defaultConfigs.tcpMaxConnections}}。</p>
</td>
</tr>
<tr>
<td>单IP TCP最大连接数</td>
<td>
<digit-input name="tcpMaxConnectionsPerIP" v-model="config.tcp.maxConnectionsPerIP" maxlength="6" size="6" style="width: 6em"></digit-input>
<p class="comment">单个IP可以连接到节点的TCP最大连接数。如果为0则默认为{{defaultConfigs.tcpMaxConnectionsPerIP}};最小值为{{defaultConfigs.tcpMinConnectionsPerIP}}。</p>
</td>
</tr>
<tr>
<td>单IP TCP新连接速率<em>(分钟)</em></td>
<td>
<div class="ui fields inline">
<div class="ui field">
<div class="ui input right labeled">
<digit-input name="tcpNewConnectionsRate" v-model="config.tcp.newConnectionsRate" maxlength="6" size="6" style="width: 6em" :min="defaultConfigs.tcpNewConnectionsMinRate"></digit-input>
<span class="ui label">个新连接/每分钟</span>
</div>
</div>
<div class="ui field" style="line-height: 2.4em">
屏蔽
</div>
<div class="ui field">
<div class="ui input right labeled">
<digit-input name="tcpNewConnectionsRateBlockTimeout" v-model="config.tcp.newConnectionsRateBlockTimeout" maxlength="6" size="6" style="width: 5em"></digit-input>
<span class="ui label">秒</span>
</div>
</div>
</div>
<p class="comment">单个IP每分钟可以创建TCP新连接的数量。如果为0则默认为{{defaultConfigs.tcpNewConnectionsMinutelyRate}};最小值为{{defaultConfigs.tcpNewConnectionsMinMinutelyRate}}。如果没有填写屏蔽时间,则只丢弃数据包。</p>
</td>
</tr>
<tr>
<td>单IP TCP新连接速率<em>(秒钟)</em></td>
<td>
<div class="ui fields inline">
<div class="ui field">
<div class="ui input right labeled">
<digit-input name="tcpNewConnectionsSecondlyRate" v-model="config.tcp.newConnectionsSecondlyRate" maxlength="6" size="6" style="width: 6em" :min="defaultConfigs.tcpNewConnectionsMinRate"></digit-input>
<span class="ui label">个新连接/每秒钟</span>
</div>
</div>
<div class="ui field" style="line-height: 2.4em">
屏蔽
</div>
<div class="ui field">
<div class="ui input right labeled">
<digit-input name="tcpNewConnectionsSecondlyRateBlockTimeout" v-model="config.tcp.newConnectionsSecondlyRateBlockTimeout" maxlength="6" size="6" style="width: 5em"></digit-input>
<span class="ui label">秒</span>
</div>
</div>
</div>
<p class="comment">单个IP每秒钟可以创建TCP新连接的数量。如果为0则默认为{{defaultConfigs.tcpNewConnectionsSecondlyRate}};最小值为{{defaultConfigs.tcpNewConnectionsMinSecondlyRate}}。如果没有填写屏蔽时间,则只丢弃数据包。</p>
</td>
</tr>
<tr>
<td>TCP端口列表</td>
<td>
<ddos-protection-ports-config-box :v-ports="config.tcp.ports" @change="changeTCPPorts"></ddos-protection-ports-config-box>
<p class="comment">在这些端口上使用当前配置。默认为53端口。</p>
</td>
</tr>
<tr>
<td>IP白名单</td>
<td>
<ddos-protection-ip-list-config-box :v-ip-list="config.tcp.allowIPList" @change="changeTCPAllowIPList"></ddos-protection-ip-list-config-box>
<p class="comment">在白名单中的IP不受当前设置的限制。</p>
</td>
</tr>
</tbody>
</table>
<div class="margin"></div>
</div>`
})

View File

@@ -35,7 +35,8 @@ Vue.component("http-access-log-config-box", {
return { return {
accessLog: accessLog, accessLog: accessLog,
hasRequestBodyField: this.vFields.$contains(8) hasRequestBodyField: this.vFields.$contains(8),
showAdvancedOptions: false
} }
}, },
methods: { methods: {
@@ -46,6 +47,9 @@ Vue.component("http-access-log-config-box", {
return v.code return v.code
}) })
this.hasRequestBodyField = this.accessLog.fields.$contains(8) this.hasRequestBodyField = this.accessLog.fields.$contains(8)
},
changeAdvanced: function (v) {
this.showAdvancedOptions = v
} }
}, },
template: `<div> template: `<div>
@@ -63,7 +67,12 @@ Vue.component("http-access-log-config-box", {
</td> </td>
</tr> </tr>
</tbody> </tbody>
<tbody v-show="((!vIsLocation && !vIsGroup) || accessLog.isPrior) && accessLog.isOn"> <tbody v-show="((!vIsLocation && !vIsGroup) || accessLog.isPrior) && accessLog.isOn">
<tr>
<td colspan="2"><more-options-indicator @change="changeAdvanced"></more-options-indicator></td>
</tr>
</tbody>
<tbody v-show="((!vIsLocation && !vIsGroup) || accessLog.isPrior) && accessLog.isOn && showAdvancedOptions">
<tr> <tr>
<td>基础信息</td> <td>基础信息</td>
<td><p class="comment" style="padding-top: 0">默认记录客户端IP、请求URL等基础信息。</p></td> <td><p class="comment" style="padding-top: 0">默认记录客户端IP、请求URL等基础信息。</p></td>

View File

@@ -1,5 +1,5 @@
Vue.component("http-access-log-partitions-box", { Vue.component("http-access-log-partitions-box", {
props: ["v-partition", "v-day"], props: ["v-partition", "v-day", "v-query"],
mounted: function () { mounted: function () {
let that = this let that = this
Tea.action("/servers/logs/partitionData") Tea.action("/servers/logs/partitionData")
@@ -11,13 +11,18 @@ Vue.component("http-access-log-partitions-box", {
resp.data.partitions.reverse().forEach(function (v) { resp.data.partitions.reverse().forEach(function (v) {
that.partitions.push({ that.partitions.push({
code: v, code: v,
isDisabled: false isDisabled: false,
hasLogs: false
}) })
}) })
if (that.partitions.length > 0) { if (that.partitions.length > 0) {
if (that.vPartition == null || that.vPartition < 0) { if (that.vPartition == null || that.vPartition < 0) {
that.selectedPartition = that.partitions[0].code that.selectedPartition = that.partitions[0].code
} }
if (that.partitions.length > 1) {
that.checkLogs()
}
} }
}) })
.post() .post()
@@ -25,7 +30,8 @@ Vue.component("http-access-log-partitions-box", {
data: function () { data: function () {
return { return {
partitions: [], partitions: [],
selectedPartition: this.vPartition selectedPartition: this.vPartition,
checkingPartition: 0
} }
}, },
methods: { methods: {
@@ -48,12 +54,43 @@ Vue.component("http-access-log-partitions-box", {
p.isDisabled = true p.isDisabled = true
} }
}) })
},
checkLogs: function () {
let that = this
let index = this.checkingPartition
let params = {
partition: index
}
let query = this.vQuery
if (query == null || query.length == 0) {
return
}
query.split("&").forEach(function (v) {
let param = v.split("=")
params[param[0]] = decodeURIComponent(param[1])
})
Tea.action("/servers/logs/hasLogs")
.params(params)
.post()
.success(function (response) {
if (response.data.hasLogs) {
// 因为是倒序所以这里需要使用总长度减去index
that.partitions[that.partitions.length - 1 - index].hasLogs = true
}
index++
if (index >= that.partitions.length) {
return
}
that.checkingPartition = index
that.checkLogs()
})
} }
}, },
template: `<div v-if="partitions.length > 1"> template: `<div v-if="partitions.length > 1">
<div class="ui divider" style="margin-bottom: 0"></div> <div class="ui divider" style="margin-bottom: 0"></div>
<div class="ui menu text small blue" style="margin-bottom: 0; margin-top: 0"> <div class="ui menu text small blue" style="margin-bottom: 0; margin-top: 0">
<a v-for="(p, index) in partitions" :href="url(p.code)" class="item" :class="{active: selectedPartition == p.code, disabled: p.isDisabled}">分表{{p.code+1}} &nbsp; &nbsp; <span class="disabled" v-if="index != partitions.length - 1">|</span></a> <a v-for="(p, index) in partitions" :href="url(p.code)" class="item" :class="{active: selectedPartition == p.code, disabled: p.isDisabled}">分表{{p.code+1}} <span v-if="p.hasLogs">&nbsp; <dot></dot></span> &nbsp; &nbsp; <span class="disabled" v-if="index != partitions.length - 1">|</span></a>
</div> </div>
<div class="ui divider" style="margin-top: 0"></div> <div class="ui divider" style="margin-top: 0"></div>
</div>` </div>`

View File

@@ -25,6 +25,7 @@ Vue.component("http-auth-config-box", {
teaweb.popup("/servers/server/settings/access/createPopup", { teaweb.popup("/servers/server/settings/access/createPopup", {
callback: function (resp) { callback: function (resp) {
that.authConfig.policyRefs.push(resp.data.policyRef) that.authConfig.policyRefs.push(resp.data.policyRef)
that.change()
}, },
height: "28em" height: "28em"
}) })
@@ -42,6 +43,7 @@ Vue.component("http-auth-config-box", {
}, },
remove: function (index) { remove: function (index) {
this.authConfig.policyRefs.$remove(index) this.authConfig.policyRefs.$remove(index)
this.change()
}, },
methodName: function (methodType) { methodName: function (methodType) {
switch (methodType) { switch (methodType) {
@@ -49,8 +51,23 @@ Vue.component("http-auth-config-box", {
return "BasicAuth" return "BasicAuth"
case "subRequest": case "subRequest":
return "子请求" return "子请求"
case "typeA":
return "URL鉴权A"
case "typeB":
return "URL鉴权B"
case "typeC":
return "URL鉴权C"
case "typeD":
return "URL鉴权D"
} }
return "" return ""
},
change: function () {
let that = this
setTimeout(function () {
// 延时通知,是为了让表单有机会变更数据
that.$emit("change", this.authConfig)
}, 100)
} }
}, },
template: `<div> template: `<div>
@@ -59,7 +76,7 @@ Vue.component("http-auth-config-box", {
<prior-checkbox :v-config="authConfig" v-if="vIsLocation"></prior-checkbox> <prior-checkbox :v-config="authConfig" v-if="vIsLocation"></prior-checkbox>
<tbody v-show="!vIsLocation || authConfig.isPrior"> <tbody v-show="!vIsLocation || authConfig.isPrior">
<tr> <tr>
<td class="title">启用认证</td> <td class="title">启用鉴权</td>
<td> <td>
<div class="ui checkbox"> <div class="ui checkbox">
<input type="checkbox" v-model="authConfig.isOn"/> <input type="checkbox" v-model="authConfig.isOn"/>
@@ -70,14 +87,14 @@ Vue.component("http-auth-config-box", {
</tbody> </tbody>
</table> </table>
<div class="margin"></div> <div class="margin"></div>
<!-- 认证方式 --> <!-- 鉴权方式 -->
<div v-show="isOn()"> <div v-show="isOn()">
<h4>认证方式</h4> <h4>鉴权方式</h4>
<table class="ui table selectable celled" v-show="authConfig.policyRefs.length > 0"> <table class="ui table selectable celled" v-show="authConfig.policyRefs.length > 0">
<thead> <thead>
<tr> <tr>
<th class="three wide">名称</th> <th class="three wide">名称</th>
<th class="three wide">认证方法</th> <th class="three wide">鉴权方法</th>
<th>参数</th> <th>参数</th>
<th class="two wide">状态</th> <th class="two wide">状态</th>
<th class="two op">操作</th> <th class="two op">操作</th>
@@ -95,6 +112,15 @@ Vue.component("http-auth-config-box", {
<span v-if="ref.authPolicy.params.method.length > 0" class="grey">[{{ref.authPolicy.params.method}}]</span> <span v-if="ref.authPolicy.params.method.length > 0" class="grey">[{{ref.authPolicy.params.method}}]</span>
{{ref.authPolicy.params.url}} {{ref.authPolicy.params.url}}
</span> </span>
<span v-if="ref.authPolicy.type == 'typeA'">{{ref.authPolicy.params.signParamName}}/有效期{{ref.authPolicy.params.life}}秒</span>
<span v-if="ref.authPolicy.type == 'typeB'">有效期{{ref.authPolicy.params.life}}秒</span>
<span v-if="ref.authPolicy.type == 'typeC'">有效期{{ref.authPolicy.params.life}}秒</span>
<span v-if="ref.authPolicy.type == 'typeD'">{{ref.authPolicy.params.signParamName}}/{{ref.authPolicy.params.timestampParamName}}/有效期{{ref.authPolicy.params.life}}秒</span>
<div v-if="(ref.authPolicy.params.exts != null && ref.authPolicy.params.exts.length > 0) || (ref.authPolicy.params.domains != null && ref.authPolicy.params.domains.length > 0)">
<grey-label v-if="ref.authPolicy.params.exts != null" v-for="ext in ref.authPolicy.params.exts">扩展名:{{ext}}</grey-label>
<grey-label v-if="ref.authPolicy.params.domains != null" v-for="domain in ref.authPolicy.params.domains">域名:{{domain}}</grey-label>
</div>
</td> </td>
<td> <td>
<label-on :v-is-on="ref.authPolicy.isOn"></label-on> <label-on :v-is-on="ref.authPolicy.isOn"></label-on>
@@ -106,7 +132,7 @@ Vue.component("http-auth-config-box", {
</tr> </tr>
</tbody> </tbody>
</table> </table>
<button class="ui button small" type="button" @click.prevent="add">+添加认证方式</button> <button class="ui button small" type="button" @click.prevent="add">+添加鉴权方式</button>
</div> </div>
<div class="margin"></div> <div class="margin"></div>
</div>` </div>`

View File

@@ -3,6 +3,14 @@ Vue.component("http-cache-ref-box", {
props: ["v-cache-ref", "v-is-reverse"], props: ["v-cache-ref", "v-is-reverse"],
mounted: function () { mounted: function () {
this.$refs.variablesDescriber.update(this.ref.key) this.$refs.variablesDescriber.update(this.ref.key)
if (this.ref.simpleCond != null) {
this.condType = this.ref.simpleCond.type
this.changeCondType(this.ref.simpleCond.type, true)
this.condCategory = "simple"
} else if (this.ref.conds != null && this.ref.conds.groups != null) {
this.condCategory = "complex"
}
this.changeCondCategory(this.condCategory)
}, },
data: function () { data: function () {
let ref = this.vCacheRef let ref = this.vCacheRef
@@ -18,7 +26,8 @@ Vue.component("http-cache-ref-box", {
skipCacheControlValues: ["private", "no-cache", "no-store"], skipCacheControlValues: ["private", "no-cache", "no-store"],
skipSetCookie: true, skipSetCookie: true,
enableRequestCachePragma: false, enableRequestCachePragma: false,
conds: null, conds: null, // 复杂条件
simpleCond: null, // 简单条件
allowChunkedEncoding: true, allowChunkedEncoding: true,
allowPartialContent: false, allowPartialContent: false,
enableIfNoneMatch: false, enableIfNoneMatch: false,
@@ -50,9 +59,22 @@ Vue.component("http-cache-ref-box", {
if (ref.minSize == null) { if (ref.minSize == null) {
ref.minSize = {count: 0, unit: "kb"} ref.minSize = {count: 0, unit: "kb"}
} }
let condType = "url-extension"
let condComponent = window.REQUEST_COND_COMPONENTS.$find(function (k, v) {
return v.type == "url-extension"
})
return { return {
ref: ref, ref: ref,
moreOptionsVisible: false moreOptionsVisible: false,
condCategory: "simple", // 条件分类simple|complex
condType: condType,
condComponent: condComponent,
condIsCaseInsensitive: (ref.simpleCond != null) ? ref.simpleCond.isCaseInsensitive : true,
components: window.REQUEST_COND_COMPONENTS
} }
}, },
methods: { methods: {
@@ -70,6 +92,7 @@ Vue.component("http-cache-ref-box", {
}, },
changeConds: function (v) { changeConds: function (v) {
this.ref.conds = v this.ref.conds = v
this.ref.simpleCond = null
}, },
changeStatusList: function (list) { changeStatusList: function (list) {
let result = [] let result = []
@@ -92,15 +115,76 @@ Vue.component("http-cache-ref-box", {
}, },
changeExpiresTime: function (expiresTime) { changeExpiresTime: function (expiresTime) {
this.ref.expiresTime = expiresTime this.ref.expiresTime = expiresTime
},
// 切换条件类型
changeCondCategory: function (condCategory) {
this.condCategory = condCategory
// resize window
let dialog = window.parent.document.querySelector("*[role='dialog']")
switch (condCategory) {
case "simple":
dialog.style.width = "40em"
break
case "complex":
let width = window.parent.innerWidth
if (width > 1024) {
width = 1024
}
dialog.style.width = width + "px"
break
}
},
changeCondType: function (condType, isInit) {
if (!isInit && this.ref.simpleCond != null) {
this.ref.simpleCond.value = null
}
let def = this.components.$find(function (k, component) {
return component.type == condType
})
if (def != null) {
this.condComponent = def
}
} }
}, },
template: `<tbody> template: `<tbody>
<tr> <tr v-if="condCategory == 'simple'">
<td class="title color-border">条件类型 *</td>
<td>
<select class="ui dropdown auto-width" name="condType" v-model="condType" @change="changeCondType(condType, false)">
<option value="url-extension">URL扩展名</option>
<option value="url-prefix">URL前缀</option>
<option value="url-eq-index">首页</option>
<option value="url-eq">URL完整路径</option>
<option value="url-regexp">URL正则匹配</option>
<option value="params">参数匹配</option>
</select>
<p class="comment"><a href="" @click.prevent="changeCondCategory('complex')">切换到复杂条件 &raquo;</a></p>
</td>
</tr>
<tr v-if="condCategory == 'simple'">
<td class="color-border">{{condComponent.paramsTitle}} *</td>
<td>
<component :is="condComponent.component" :v-cond="ref.simpleCond"></component>
</td>
</tr>
<tr v-if="condCategory == 'simple' && condComponent.caseInsensitive">
<td class="color-border">不区分大小写</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="condIsCaseInsensitive" value="1" v-model="condIsCaseInsensitive"/>
<label></label>
</div>
<p class="comment">选中后表示对比时忽略参数值的大小写。</p>
</td>
</tr>
<tr v-if="condCategory == 'complex'">
<td class="title">匹配条件分组 *</td> <td class="title">匹配条件分组 *</td>
<td> <td>
<http-request-conds-box :v-conds="ref.conds" @change="changeConds"></http-request-conds-box> <http-request-conds-box :v-conds="ref.conds" @change="changeConds"></http-request-conds-box>
<p class="comment"><a href="" @click.prevent="changeCondCategory('simple')">&laquo; 切换到简单条件</a></p>
<input type="hidden" name="cacheRefJSON" :value="JSON.stringify(ref)"/>
</td> </td>
</tr> </tr>
<tr v-show="!vIsReverse"> <tr v-show="!vIsReverse">
@@ -208,5 +292,8 @@ Vue.component("http-cache-ref-box", {
<p class="comment">特殊情况下才需要开启,可能会降低缓存命中率。</p> <p class="comment">特殊情况下才需要开启,可能会降低缓存命中率。</p>
</td> </td>
</tr> </tr>
<tr v-show="false">
<td colspan="2"><input type="hidden" name="cacheRefJSON" :value="JSON.stringify(ref)"/></td>
</tr>
</tbody>` </tbody>`
}) })

View File

@@ -43,7 +43,10 @@ Vue.component("http-cache-refs-box", {
</tr> </tr>
<tr v-for="(cacheRef, index) in refs"> <tr v-for="(cacheRef, index) in refs">
<td :class="{'color-border': cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}"> <td :class="{'color-border': cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}">
<http-request-conds-view :v-conds="cacheRef.conds" :class="{disabled: !cacheRef.isOn}"></http-request-conds-view> <http-request-conds-view :v-conds="cacheRef.conds" :class="{disabled: !cacheRef.isOn}" v-if="cacheRef.conds != null && cacheRef.conds.groups != null"></http-request-conds-view>
<http-request-cond-view :v-cond="cacheRef.simpleCond" v-if="cacheRef.simpleCond != null"></http-request-cond-view>
<!-- 特殊参数 -->
<grey-label v-if="cacheRef.minSize != null && cacheRef.minSize.count > 0"> <grey-label v-if="cacheRef.minSize != null && cacheRef.minSize.count > 0">
{{cacheRef.minSize.count}}{{cacheRef.minSize.unit}} {{cacheRef.minSize.count}}{{cacheRef.minSize.unit}}
<span v-if="cacheRef.maxSize != null && cacheRef.maxSize.count > 0">- {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}}</span> <span v-if="cacheRef.maxSize != null && cacheRef.maxSize.count > 0">- {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}}</span>

View File

@@ -35,17 +35,12 @@ Vue.component("http-cache-refs-config-box", {
addRef: function (isReverse) { addRef: function (isReverse) {
window.UPDATING_CACHE_REF = null window.UPDATING_CACHE_REF = null
let width = window.innerWidth
if (width > 1024) {
width = 1024
}
let height = window.innerHeight let height = window.innerHeight
if (height > 500) { if (height > 500) {
height = 500 height = 500
} }
let that = this let that = this
teaweb.popup("/servers/server/settings/cache/createPopup?isReverse=" + (isReverse ? 1 : 0), { teaweb.popup("/servers/server/settings/cache/createPopup?isReverse=" + (isReverse ? 1 : 0), {
width: width + "px",
height: height + "px", height: height + "px",
callback: function (resp) { callback: function (resp) {
let newRef = resp.data.cacheRef let newRef = resp.data.cacheRef
@@ -82,23 +77,18 @@ Vue.component("http-cache-refs-config-box", {
updateRef: function (index, cacheRef) { updateRef: function (index, cacheRef) {
window.UPDATING_CACHE_REF = cacheRef window.UPDATING_CACHE_REF = cacheRef
let width = window.innerWidth
if (width > 1024) {
width = 1024
}
let height = window.innerHeight let height = window.innerHeight
if (height > 500) { if (height > 500) {
height = 500 height = 500
} }
let that = this let that = this
teaweb.popup("/servers/server/settings/cache/createPopup", { teaweb.popup("/servers/server/settings/cache/createPopup", {
width: width + "px",
height: height + "px", height: height + "px",
callback: function (resp) { callback: function (resp) {
resp.data.cacheRef.id = that.refs[index].id resp.data.cacheRef.id = that.refs[index].id
Vue.set(that.refs, index, resp.data.cacheRef) Vue.set(that.refs, index, resp.data.cacheRef)
that.change() that.change()
that.$refs.cacheRef[index].updateConds(resp.data.cacheRef.conds) that.$refs.cacheRef[index].updateConds(resp.data.cacheRef.conds, resp.data.cacheRef.simpleCond)
that.$refs.cacheRef[index].notifyChange() that.$refs.cacheRef[index].notifyChange()
} }
}) })
@@ -186,7 +176,10 @@ Vue.component("http-cache-refs-config-box", {
<tr> <tr>
<td style="text-align: center;"><i class="icon bars handle grey"></i> </td> <td style="text-align: center;"><i class="icon bars handle grey"></i> </td>
<td :class="{'color-border': cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}"> <td :class="{'color-border': cacheRef.conds.connector == 'and', disabled: !cacheRef.isOn}" :style="{'border-left':cacheRef.isReverse ? '1px #db2828 solid' : ''}">
<http-request-conds-view :v-conds="cacheRef.conds" ref="cacheRef" :class="{disabled: !cacheRef.isOn}"></http-request-conds-view> <http-request-conds-view :v-conds="cacheRef.conds" ref="cacheRef" :class="{disabled: !cacheRef.isOn}" v-if="cacheRef.conds != null && cacheRef.conds.groups != null"></http-request-conds-view>
<http-request-cond-view :v-cond="cacheRef.simpleCond" ref="cacheRef" v-if="cacheRef.simpleCond != null"></http-request-cond-view>
<!-- 特殊参数 -->
<grey-label v-if="cacheRef.minSize != null && cacheRef.minSize.count > 0"> <grey-label v-if="cacheRef.minSize != null && cacheRef.minSize.count > 0">
{{cacheRef.minSize.count}}{{cacheRef.minSize.unit}} {{cacheRef.minSize.count}}{{cacheRef.minSize.unit}}
<span v-if="cacheRef.maxSize != null && cacheRef.maxSize.count > 0">- {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}}</span> <span v-if="cacheRef.maxSize != null && cacheRef.maxSize.count > 0">- {{cacheRef.maxSize.count}}{{cacheRef.maxSize.unit}}</span>
@@ -217,7 +210,7 @@ Vue.component("http-cache-refs-config-box", {
</table> </table>
<p class="comment" v-if="refs.length > 1">所有条件匹配顺序为从上到下,可以拖动左侧的<i class="icon bars"></i>排序。服务设置的优先级比全局缓存策略设置的优先级要高。</p> <p class="comment" v-if="refs.length > 1">所有条件匹配顺序为从上到下,可以拖动左侧的<i class="icon bars"></i>排序。服务设置的优先级比全局缓存策略设置的优先级要高。</p>
<button class="ui button tiny" @click.prevent="addRef(false)" type="button">+添加缓存设置</button> &nbsp; &nbsp; <a href="" @click.prevent="addRef(true)">+添加不缓存设置</a> <button class="ui button tiny" @click.prevent="addRef(false)" type="button">+添加缓存条件</button> &nbsp; &nbsp; <a href="" @click.prevent="addRef(true)">+添加不缓存条件</a>
</div> </div>
<div class="margin"></div> <div class="margin"></div>
</div>` </div>`

View File

@@ -70,7 +70,7 @@ Vue.component("http-cond-url-extension", {
template: `<div> template: `<div>
<input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/> <input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/>
<div v-if="extensions.length > 0"> <div v-if="extensions.length > 0">
<div class="ui label small" v-for="(ext, index) in extensions">{{ext}} <a href="" title="删除" @click.prevent="removeExt(index)"><i class="icon remove"></i></a></div> <div class="ui label small basic" v-for="(ext, index) in extensions">{{ext}} <a href="" title="删除" @click.prevent="removeExt(index)"><i class="icon remove"></i></a></div>
<div class="ui divider"></div> <div class="ui divider"></div>
</div> </div>
<div class="ui fields inline" v-if="isAdding"> <div class="ui fields inline" v-if="isAdding">
@@ -78,18 +78,18 @@ Vue.component("http-cond-url-extension", {
<input type="text" size="6" maxlength="100" v-model="addingExt" ref="addingExt" placeholder=".xxx" @keyup.enter="confirmAdding" @keypress.enter.prevent="1" /> <input type="text" size="6" maxlength="100" v-model="addingExt" ref="addingExt" placeholder=".xxx" @keyup.enter="confirmAdding" @keypress.enter.prevent="1" />
</div> </div>
<div class="ui field"> <div class="ui field">
<button class="ui button tiny" type="button" @click.prevent="confirmAdding">确认</button> <button class="ui button tiny basic" type="button" @click.prevent="confirmAdding">确认</button>
<a href="" title="取消" @click.prevent="cancelAdding"><i class="icon remove"></i></a> <a href="" title="取消" @click.prevent="cancelAdding"><i class="icon remove"></i></a>
</div> </div>
</div> </div>
<div style="margin-top: 1em"> <div style="margin-top: 1em" v-show="!isAdding">
<button class="ui button tiny" type="button" @click.prevent="addExt()">+添加扩展名</button> <button class="ui button tiny basic" type="button" @click.prevent="addExt()">+添加扩展名</button>
</div> </div>
<p class="comment">扩展名需要包含点(.)符号,例如<span class="ui label tiny">.jpg</span>、<span class="ui label tiny">.png</span>之类。</p> <p class="comment">扩展名需要包含点(.)符号,例如<code-label>.jpg</code-label>、<code-label>.png</code-label>之类。</p>
</div>` </div>`
}) })
// URL扩展名条件 // 排除URL扩展名条件
Vue.component("http-cond-url-not-extension", { Vue.component("http-cond-url-not-extension", {
props: ["v-cond"], props: ["v-cond"],
data: function () { data: function () {
@@ -161,7 +161,7 @@ Vue.component("http-cond-url-not-extension", {
template: `<div> template: `<div>
<input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/> <input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/>
<div v-if="extensions.length > 0"> <div v-if="extensions.length > 0">
<div class="ui label small" v-for="(ext, index) in extensions">{{ext}} <a href="" title="删除" @click.prevent="removeExt(index)"><i class="icon remove"></i></a></div> <div class="ui label small basic" v-for="(ext, index) in extensions">{{ext}} <a href="" title="删除" @click.prevent="removeExt(index)"><i class="icon remove"></i></a></div>
<div class="ui divider"></div> <div class="ui divider"></div>
</div> </div>
<div class="ui fields inline" v-if="isAdding"> <div class="ui fields inline" v-if="isAdding">
@@ -169,20 +169,23 @@ Vue.component("http-cond-url-not-extension", {
<input type="text" size="6" maxlength="100" v-model="addingExt" ref="addingExt" placeholder=".xxx" @keyup.enter="confirmAdding" @keypress.enter.prevent="1" /> <input type="text" size="6" maxlength="100" v-model="addingExt" ref="addingExt" placeholder=".xxx" @keyup.enter="confirmAdding" @keypress.enter.prevent="1" />
</div> </div>
<div class="ui field"> <div class="ui field">
<button class="ui button tiny" type="button" @click.prevent="confirmAdding">确认</button> <button class="ui button tiny basic" type="button" @click.prevent="confirmAdding">确认</button>
<a href="" title="取消" @click.prevent="cancelAdding"><i class="icon remove"></i></a> <a href="" title="取消" @click.prevent="cancelAdding"><i class="icon remove"></i></a>
</div> </div>
</div> </div>
<div style="margin-top: 1em"> <div style="margin-top: 1em" v-show="!isAdding">
<button class="ui button tiny" type="button" @click.prevent="addExt()">+添加扩展名</button> <button class="ui button tiny basic" type="button" @click.prevent="addExt()">+添加扩展名</button>
</div> </div>
<p class="comment">扩展名需要包含点(.)符号,例如<span class="ui label tiny">.jpg</span>、<span class="ui label tiny">.png</span>之类。</p> <p class="comment">扩展名需要包含点(.)符号,例如<code-label>.jpg</code-label>、<code-label>.png</code-label>之类。</p>
</div>` </div>`
}) })
// 根据URL前缀 // 根据URL前缀
Vue.component("http-cond-url-prefix", { Vue.component("http-cond-url-prefix", {
props: ["v-cond"], props: ["v-cond"],
mounted: function () {
this.$refs.valueInput.focus()
},
data: function () { data: function () {
let cond = { let cond = {
isRequest: true, isRequest: true,
@@ -205,13 +208,16 @@ Vue.component("http-cond-url-prefix", {
}, },
template: `<div> template: `<div>
<input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/> <input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/>
<input type="text" v-model="cond.value"/> <input type="text" v-model="cond.value" ref="valueInput"/>
<p class="comment">URL前缀有此前缀的URL都将会被匹配通常以<code-label>/</code-label>开头,比如<code-label>/static</code-label>。</p> <p class="comment">URL前缀有此前缀的URL都将会被匹配通常以<code-label>/</code-label>开头,比如<code-label>/static</code-label>、<code-label>/images</code-label>,不需要带域名。</p>
</div>` </div>`
}) })
Vue.component("http-cond-url-not-prefix", { Vue.component("http-cond-url-not-prefix", {
props: ["v-cond"], props: ["v-cond"],
mounted: function () {
this.$refs.valueInput.focus()
},
data: function () { data: function () {
let cond = { let cond = {
isRequest: true, isRequest: true,
@@ -235,14 +241,47 @@ Vue.component("http-cond-url-not-prefix", {
}, },
template: `<div> template: `<div>
<input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/> <input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/>
<input type="text" v-model="cond.value"/> <input type="text" v-model="cond.value" ref="valueInput"/>
<p class="comment">要排除的URL前缀有此前缀的URL都将会被匹配通常以<code-label>/</code-label>开头,比如<code-label>/static</code-label>。</p> <p class="comment">要排除的URL前缀有此前缀的URL都将会被匹配通常以<code-label>/</code-label>开头,比如<code-label>/static</code-label>、<code-label>/images</code-label>,不需要带域名。</p>
</div>`
})
// 首页
Vue.component("http-cond-url-eq-index", {
props: ["v-cond"],
data: function () {
let cond = {
isRequest: true,
param: "${requestPath}",
operator: "eq",
value: "/",
isCaseInsensitive: false
}
if (this.vCond != null && typeof this.vCond.value == "string") {
cond.value = this.vCond.value
}
return {
cond: cond
}
},
methods: {
changeCaseInsensitive: function (isCaseInsensitive) {
this.cond.isCaseInsensitive = isCaseInsensitive
}
},
template: `<div>
<input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/>
<input type="text" v-model="cond.value" disabled="disabled" style="background: #eee"/>
<p class="comment">检查URL路径是为<code-label>/</code-label>,不需要带域名。</p>
</div>` </div>`
}) })
// URL精准匹配 // URL精准匹配
Vue.component("http-cond-url-eq", { Vue.component("http-cond-url-eq", {
props: ["v-cond"], props: ["v-cond"],
mounted: function () {
this.$refs.valueInput.focus()
},
data: function () { data: function () {
let cond = { let cond = {
isRequest: true, isRequest: true,
@@ -265,13 +304,16 @@ Vue.component("http-cond-url-eq", {
}, },
template: `<div> template: `<div>
<input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/> <input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/>
<input type="text" v-model="cond.value"/> <input type="text" v-model="cond.value" ref="valueInput"/>
<p class="comment">完整的URL路径通常以<code-label>/</code-label>开头,比如<code-label>/static/ui.js</code-label>并不包含域名部分。</p> <p class="comment">完整的URL路径通常以<code-label>/</code-label>开头,比如<code-label>/static/ui.js</code-label>不需要带域名。</p>
</div>` </div>`
}) })
Vue.component("http-cond-url-not-eq", { Vue.component("http-cond-url-not-eq", {
props: ["v-cond"], props: ["v-cond"],
mounted: function () {
this.$refs.valueInput.focus()
},
data: function () { data: function () {
let cond = { let cond = {
isRequest: true, isRequest: true,
@@ -295,14 +337,17 @@ Vue.component("http-cond-url-not-eq", {
}, },
template: `<div> template: `<div>
<input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/> <input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/>
<input type="text" v-model="cond.value"/> <input type="text" v-model="cond.value" ref="valueInput"/>
<p class="comment">要排除的完整的URL路径通常以<code-label>/</code-label>开头,比如<code-label>/static/ui.js</code-label>并不包含域名部分。</p> <p class="comment">要排除的完整的URL路径通常以<code-label>/</code-label>开头,比如<code-label>/static/ui.js</code-label>不需要带域名。</p>
</div>` </div>`
}) })
// URL正则匹配 // URL正则匹配
Vue.component("http-cond-url-regexp", { Vue.component("http-cond-url-regexp", {
props: ["v-cond"], props: ["v-cond"],
mounted: function () {
this.$refs.valueInput.focus()
},
data: function () { data: function () {
let cond = { let cond = {
isRequest: true, isRequest: true,
@@ -325,14 +370,17 @@ Vue.component("http-cond-url-regexp", {
}, },
template: `<div> template: `<div>
<input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/> <input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/>
<input type="text" v-model="cond.value"/> <input type="text" v-model="cond.value" ref="valueInput"/>
<p class="comment">匹配URL的正则表达式比如<code-label>^/static/(.*).js$</code-label>。</p> <p class="comment">匹配URL的正则表达式比如<code-label>^/static/(.*).js$</code-label>,不需要带域名。</p>
</div>` </div>`
}) })
// 排除URL正则匹配 // 排除URL正则匹配
Vue.component("http-cond-url-not-regexp", { Vue.component("http-cond-url-not-regexp", {
props: ["v-cond"], props: ["v-cond"],
mounted: function () {
this.$refs.valueInput.focus()
},
data: function () { data: function () {
let cond = { let cond = {
isRequest: true, isRequest: true,
@@ -355,8 +403,8 @@ Vue.component("http-cond-url-not-regexp", {
}, },
template: `<div> template: `<div>
<input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/> <input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/>
<input type="text" v-model="cond.value"/> <input type="text" v-model="cond.value" ref="valueInput"/>
<p class="comment"><strong>不要</strong>匹配URL的正则表达式意即只要匹配成功则排除此条件比如<code-label>^/static/(.*).js$</code-label>。</p> <p class="comment"><strong>不要</strong>匹配URL的正则表达式意即只要匹配成功则排除此条件比如<code-label>^/static/(.*).js$</code-label>,不需要带域名。</p>
</div>` </div>`
}) })
@@ -364,6 +412,9 @@ Vue.component("http-cond-url-not-regexp", {
// User-Agent正则匹配 // User-Agent正则匹配
Vue.component("http-cond-user-agent-regexp", { Vue.component("http-cond-user-agent-regexp", {
props: ["v-cond"], props: ["v-cond"],
mounted: function () {
this.$refs.valueInput.focus()
},
data: function () { data: function () {
let cond = { let cond = {
isRequest: true, isRequest: true,
@@ -386,7 +437,7 @@ Vue.component("http-cond-user-agent-regexp", {
}, },
template: `<div> template: `<div>
<input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/> <input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/>
<input type="text" v-model="cond.value"/> <input type="text" v-model="cond.value" ref="valueInput"/>
<p class="comment">匹配User-Agent的正则表达式比如<code-label>Android|iPhone</code-label>。</p> <p class="comment">匹配User-Agent的正则表达式比如<code-label>Android|iPhone</code-label>。</p>
</div>` </div>`
}) })
@@ -394,6 +445,9 @@ Vue.component("http-cond-user-agent-regexp", {
// User-Agent正则不匹配 // User-Agent正则不匹配
Vue.component("http-cond-user-agent-not-regexp", { Vue.component("http-cond-user-agent-not-regexp", {
props: ["v-cond"], props: ["v-cond"],
mounted: function () {
this.$refs.valueInput.focus()
},
data: function () { data: function () {
let cond = { let cond = {
isRequest: true, isRequest: true,
@@ -416,7 +470,7 @@ Vue.component("http-cond-user-agent-not-regexp", {
}, },
template: `<div> template: `<div>
<input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/> <input type="hidden" name="condJSON" :value="JSON.stringify(cond)"/>
<input type="text" v-model="cond.value"/> <input type="text" v-model="cond.value" ref="valueInput"/>
<p class="comment">匹配User-Agent的正则表达式比如<code-label>Android|iPhone</code-label>,如果匹配,则排除此条件。</p> <p class="comment">匹配User-Agent的正则表达式比如<code-label>Android|iPhone</code-label>,如果匹配,则排除此条件。</p>
</div>` </div>`
}) })
@@ -490,12 +544,12 @@ Vue.component("http-cond-mime-type", {
<input type="text" size="16" maxlength="100" v-model="addingMimeType" ref="addingMimeType" placeholder="类似于image/png" @keyup.enter="confirmAdding" @keypress.enter.prevent="1" /> <input type="text" size="16" maxlength="100" v-model="addingMimeType" ref="addingMimeType" placeholder="类似于image/png" @keyup.enter="confirmAdding" @keypress.enter.prevent="1" />
</div> </div>
<div class="ui field"> <div class="ui field">
<button class="ui button tiny" type="button" @click.prevent="confirmAdding">确认</button> <button class="ui button tiny basic" type="button" @click.prevent="confirmAdding">确认</button>
<a href="" title="取消" @click.prevent="cancelAdding"><i class="icon remove"></i></a> <a href="" title="取消" @click.prevent="cancelAdding"><i class="icon remove"></i></a>
</div> </div>
</div> </div>
<div style="margin-top: 1em"> <div style="margin-top: 1em">
<button class="ui button tiny" type="button" @click.prevent="addMimeType()">+添加MimeType</button> <button class="ui button tiny basic" type="button" @click.prevent="addMimeType()">+添加MimeType</button>
</div> </div>
<p class="comment">服务器返回的内容的MimeType比如<span class="ui label tiny">text/html</span>、<span class="ui label tiny">image/*</span>等。</p> <p class="comment">服务器返回的内容的MimeType比如<span class="ui label tiny">text/html</span>、<span class="ui label tiny">image/*</span>等。</p>
</div>` </div>`

View File

@@ -78,8 +78,11 @@ Vue.component("http-firewall-actions-box", {
captchaLife: "", captchaLife: "",
captchaMaxFails: "", captchaMaxFails: "",
captchaFailBlockTimeout: "", captchaFailBlockTimeout: "",
get302Life: "", get302Life: "",
post307Life: "", post307Life: "",
recordIPType: "black", recordIPType: "black",
recordIPLevel: "critical", recordIPLevel: "critical",
recordIPTimeout: "", recordIPTimeout: "",
@@ -97,7 +100,11 @@ Vue.component("http-firewall-actions-box", {
goGroup: null, goGroup: null,
goSetId: 0, goSetId: 0,
goSetName: "" goSetName: "",
jsCookieLife: "",
jsCookieMaxFails: "",
jsCookieFailBlockTimeout: ""
} }
}, },
watch: { watch: {
@@ -195,7 +202,31 @@ Vue.component("http-firewall-actions-box", {
} else { } else {
this.goSetName = set.name this.goSetName = set.name
} }
} },
jsCookieLife: function (v) {
v = parseInt(v)
if (isNaN(v)) {
this.actionOptions["life"] = 0
} else {
this.actionOptions["life"] = v
}
},
jsCookieMaxFails: function (v) {
v = parseInt(v)
if (isNaN(v)) {
this.actionOptions["maxFails"] = 0
} else {
this.actionOptions["maxFails"] = v
}
},
jsCookieFailBlockTimeout: function (v) {
v = parseInt(v)
if (isNaN(v)) {
this.actionOptions["failBlockTimeout"] = 0
} else {
this.actionOptions["failBlockTimeout"] = v
}
},
}, },
methods: { methods: {
add: function () { add: function () {
@@ -207,10 +238,17 @@ Vue.component("http-firewall-actions-box", {
// 动作参数 // 动作参数
this.blockTimeout = "" this.blockTimeout = ""
this.blockScope = "global" this.blockScope = "global"
this.captchaLife = "" this.captchaLife = ""
this.captchaMaxFails = "" this.captchaMaxFails = ""
this.captchaFailBlockTimeout = "" this.captchaFailBlockTimeout = ""
this.jsCookieLife = ""
this.jsCookieMaxFails = ""
this.jsCookieFailBlockTimeout = ""
this.get302Life = "" this.get302Life = ""
this.post307Life = "" this.post307Life = ""
this.recordIPLevel = "critical" this.recordIPLevel = "critical"
@@ -287,6 +325,20 @@ Vue.component("http-firewall-actions-box", {
this.captchaFailBlockTimeout = config.options.failBlockTimeout.toString() this.captchaFailBlockTimeout = config.options.failBlockTimeout.toString()
} }
break break
case "js_cookie":
this.jsCookieLife = ""
if (config.options.life != null || config.options.life > 0) {
this.jsCookieLife = config.options.life.toString()
}
this.jsCookieMaxFails = ""
if (config.options.maxFails != null || config.options.maxFails > 0) {
this.jsCookieMaxFails = config.options.maxFails.toString()
}
this.jsCookieFailBlockTimeout = ""
if (config.options.failBlockTimeout != null || config.options.failBlockTimeout > 0) {
this.jsCookieFailBlockTimeout = config.options.failBlockTimeout.toString()
}
break
case "notify": case "notify":
break break
case "get_302": case "get_302":
@@ -539,6 +591,11 @@ Vue.component("http-firewall-actions-box", {
<span v-if="config.options.maxFails > 0"> / 最多失败{{config.options.maxFails}}次</span> <span v-if="config.options.maxFails > 0"> / 最多失败{{config.options.maxFails}}次</span>
</span> </span>
<!-- js cookie -->
<span v-if="config.code == 'js_cookie' && config.options.life > 0">:有效期{{config.options.life}}秒
<span v-if="config.options.maxFails > 0"> / 最多失败{{config.options.maxFails}}次</span>
</span>
<!-- get 302 --> <!-- get 302 -->
<span v-if="config.code == 'get_302' && config.options.life > 0">:有效期{{config.options.life}}秒</span> <span v-if="config.code == 'get_302' && config.options.life > 0">:有效期{{config.options.life}}秒</span>
@@ -638,6 +695,38 @@ Vue.component("http-firewall-actions-box", {
</td> </td>
</tr> </tr>
<!-- js cookie -->
<tr v-if="actionCode == 'js_cookie'">
<td>有效时间</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="9" v-model="jsCookieLife" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span>
</div>
<p class="comment">验证通过后在这个时间内不再验证如果为空或者为0表示默认。</p>
</td>
</tr>
<tr v-if="actionCode == 'js_cookie'">
<td>最多失败次数</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="9" v-model="jsCookieMaxFails" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">次</span>
</div>
<p class="comment">允许用户失败尝试的最多次数超过这个次数将被自动加入黑名单如果为空或者为0表示默认。</p>
</td>
</tr>
<tr v-if="actionCode == 'js_cookie'">
<td>失败拦截时间</td>
<td>
<div class="ui input right labeled">
<input type="text" style="width: 5em" maxlength="9" v-model="jsCookieFailBlockTimeout" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">秒</span>
</div>
<p class="comment">在达到最多失败次数大于0自动拦截的时间如果为空或者为0表示默认。</p>
</td>
</tr>
<!-- get_302 --> <!-- get_302 -->
<tr v-if="actionCode == 'get_302'"> <tr v-if="actionCode == 'get_302'">
<td>有效时间</td> <td>有效时间</td>

View File

@@ -183,6 +183,13 @@ Vue.component("http-firewall-checkpoint-cc", {
value: ignoreCommonFiles value: ignoreCommonFiles
} }
] ]
},
thresholdTooLow: function () {
let threshold = parseInt(this.threshold.toString())
if (isNaN(threshold) || threshold <= 0) {
threshold = 1000
}
return threshold > 0 && threshold < 5
} }
}, },
template: `<div> template: `<div>
@@ -208,6 +215,7 @@ Vue.component("http-firewall-checkpoint-cc", {
<td>阈值 *</td> <td>阈值 *</td>
<td> <td>
<input type="text" v-model="threshold" style="width: 6em" maxlength="8"/> <input type="text" v-model="threshold" style="width: 6em" maxlength="8"/>
<p class="comment" v-if="thresholdTooLow()"><span class="red">对于网站类应用来说,当前阈值设置的太低,有可能会影响用户正常访问。</span></p>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@@ -0,0 +1,38 @@
Vue.component("http-request-cond-view", {
props: ["v-cond"],
data: function () {
return {
cond: this.vCond,
components: window.REQUEST_COND_COMPONENTS
}
},
methods: {
typeName: function (cond) {
let c = this.components.$find(function (k, v) {
return v.type == cond.type
})
if (c != null) {
return c.name;
}
return cond.param + " " + cond.operator
},
updateConds: function (conds, simpleCond) {
for (let k in simpleCond) {
if (simpleCond.hasOwnProperty(k)) {
this.cond[k] = simpleCond[k]
}
}
},
notifyChange: function () {
}
},
template: `<div style="margin-bottom: 0.5em">
<span class="ui label small basic">
<var v-if="cond.type.length == 0 || cond.type == 'params'" style="font-style: normal">{{cond.param}} <var>{{cond.operator}}</var></var>
<var v-if="cond.type.length > 0 && cond.type != 'params'" style="font-style: normal">{{typeName(cond)}}: </var>
{{cond.value}}
<sup v-if="cond.isCaseInsensitive" title="不区分大小写"><i class="icon info small"></i></sup>
</span>
</div>`
})

View File

@@ -9,6 +9,9 @@ Vue.component("http-request-conds-box", {
groups: [] groups: []
} }
} }
if (conds.groups == null) {
conds.groups = []
}
return { return {
conds: conds, conds: conds,
components: window.REQUEST_COND_COMPONENTS components: window.REQUEST_COND_COMPONENTS
@@ -102,7 +105,7 @@ Vue.component("http-request-conds-box", {
</table> </table>
<div> <div>
<button class="ui button tiny" type="button" @click.prevent="addGroup()">+添加分组</button> <button class="ui button tiny basic" type="button" @click.prevent="addGroup()">+添加分组</button>
</div> </div>
</div> </div>
</div>` </div>`

View File

@@ -10,6 +10,9 @@ Vue.component("http-request-conds-view", {
groups: [] groups: []
} }
} }
if (conds.groups == null) {
conds.groups = []
}
let that = this let that = this
conds.groups.forEach(function (group) { conds.groups.forEach(function (group) {
@@ -43,12 +46,14 @@ Vue.component("http-request-conds-view", {
}, },
notifyChange: function () { notifyChange: function () {
let that = this let that = this
this.initConds.groups.forEach(function (group) { if (this.initConds.groups != null) {
group.conds.forEach(function (cond) { this.initConds.groups.forEach(function (group) {
cond.typeName = that.typeName(cond) group.conds.forEach(function (cond) {
cond.typeName = that.typeName(cond)
})
}) })
}) this.$forceUpdate()
this.$forceUpdate() }
} }
}, },
template: `<div> template: `<div>

View File

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

View File

@@ -7,6 +7,15 @@
"paramsTitle": "扩展名列表", "paramsTitle": "扩展名列表",
"isRequest": true "isRequest": true
}, },
{
"type": "url-eq-index",
"name": "首页",
"description": "检查URL路径是为\"/\"",
"component": "http-cond-url-eq-index",
"paramsTitle": "URL完整路径",
"isRequest": true,
"caseInsensitive": false
},
{ {
"type": "url-prefix", "type": "url-prefix",
"name": "URL前缀", "name": "URL前缀",
@@ -18,7 +27,7 @@
}, },
{ {
"type": "url-eq", "type": "url-eq",
"name": "URL精准匹配", "name": "URL完整路径",
"description": "检查URL中的文件路径是否一致", "description": "检查URL中的文件路径是否一致",
"component": "http-cond-url-eq", "component": "http-cond-url-eq",
"paramsTitle": "URL完整路径", "paramsTitle": "URL完整路径",
@@ -70,7 +79,7 @@
}, },
{ {
"type": "url-not-eq", "type": "url-not-eq",
"name": "排除URL精准匹配", "name": "排除URL完整路径",
"description": "检查URL中的文件路径是否一致", "description": "检查URL中的文件路径是否一致",
"component": "http-cond-url-not-eq", "component": "http-cond-url-not-eq",
"paramsTitle": "URL完整路径", "paramsTitle": "URL完整路径",

View File

@@ -561,6 +561,7 @@ window.teaweb = {
let chartBox = document.getElementById(chartId) let chartBox = document.getElementById(chartId)
if (chartBox == null) { if (chartBox == null) {
console.error("chart id '" + chartId + "' not found")
return return
} }
let chart = this.initChart(chartBox) let chart = this.initChart(chartBox)

View File

@@ -58,6 +58,7 @@ div.comment {
color: rgba(0, 0, 0, 0.4); color: rgba(0, 0, 0, 0.4);
padding-top: 0.4em; padding-top: 0.4em;
font-weight: normal; font-weight: normal;
word-break: break-all;
} }
p.comment em, p.comment em,
div.comment em { div.comment em {

View File

@@ -1 +1 @@
{"version":3,"sources":["@layout_popup.less"],"names":[],"mappings":";AACA;EACC,WAAA;;AAGD;EACC,aAAA;;AAGD;EACC,qBAAA;;AAGD,CAAC;AAAW,CAAC,SAAS;AAAQ,CAAC,SAAS;AAAS,IAAI;EACpD,WAAA;;AAGD,CAAC;AAAU,IAAI;AAAU,IAAI;EAC5B,cAAA;;AAGD,IAAI;AAAO,KAAK;AAAO,CAAC;EACvB,sBAAA;;AAGD,CAAC;EACA,iBAAA;;AAGD,IAAI;AAAM,GAAG;EACZ,cAAA;;AAGD,IAAI;EACH,cAAA;;AAGD,GAAG,IAAI;EACN,mBAAmB,8CAAnB;;AAGD;EACC,uBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAO;AAAI,MAAO;EACjB,gBAAA;;AAGD,CAAC;AAAU,GAAG;EACb,yBAAA;EACA,kBAAA;EACA,mBAAA;;AAGD,CAAC,QAAS;AAAI,GAAG,QAAS;EACzB,6BAAA;;AAGD;EACC,mBAAA;EACA,2BAAA;EACA,gBAAA;EACA,uBAAA;;AAGD,GAAG;AAAS,CAAC;EACZ,eAAA;;;AAID,GAAG;EACF,UAAA;;AAGD,GAAG;EACF,YAAA;;AAGD,GAAG;EACF,UAAA;;AAGD,GAAG;EACF,WAAA;;;AAID,MAAM;EACL,cAAA;;;AAID;EACC,kBAAA;EACA,UAAA;EACA,UAAA;EACA,mBAAA;EACA,kBAAA;EACA,UAAA;;AAGD,mBAAqC;EACpC;IACC,SAAA;;;AAIF,KAAK;EACJ,SAAA;;AAGD,KAAK;EACJ,UAAA;;AAGD,mBAAqC;EACpC,KAAK;IACJ,SAAA;;;AAIF,KAAM,MAAM,GAAE;EACb,WAAA;;AAGD,KAAM,MAAM,GAAE;EACb,WAAA;;AAGD,KAAM,MAAM;EACX,mBAAA;;AAGD,KAAM,GAAE;EACP,8BAAA;;AAGD,KAAM,MAAM,GAAE;EACb,mBAAA;;AAGD,KAAM,MAAM,GAAE;EACb,sBAAA;;AAGD,KAAM,MAAM,GAAE,aAAc;EAC3B,mBAAA;;AAGD,KAAM,MAAM,GAAG;EACd,mBAAA;EACA,kBAAA;EACA,gBAAA;;AAGD,KAAM;EACL,mBAAA;EACA,iBAAA;;AAGD,KAAM,GAAG;EACR,gBAAA;;AAGD,KAAM,GAAG,KAAI;EACZ,cAAA;;AAGD,KAAM,GAAG;EACR,gBAAA;EACA,0BAAA;EACA,UAAA;;AAGD,KAAM,GAAG,EAAC;EACT,SAAS,GAAT;;AAGD,KAAM,GAAG,EAAC;EACT,SAAS,GAAT;;AAGD,KAAM;EACL,mBAAA;;AAGD,KAAM,GAAG,KAAI;EACZ,gBAAA;;AAGD,KAAM,QAAO;EACZ,gBAAA;EACA,cAAA;EACA,gBAAA;;;AAID,KAAK;EACJ,gBAAA;;AAGD,KAAK,KAAK;EACT,UAAA;EACA,WAAA;;;AAID;EACC,wBAAA;;;AAID,iBAAkB;EACjB,gBAAA;;AAGD,iBAAkB,MAAK;EACtB,UAAA;;AAGD,iBAAkB,MAAM;EACvB,2BAAA;;AAGD,MAAM;EACL,sBAAA;;;AAID,mBAAqC;EACpC,OAAO,IAAI;IACV,sBAAA;;;;AAKF,KAAK;EACJ,0BAAA;;AAGD,KAAK;EACJ,cAAA;;;AAOD,WAAY,MAAK;EAChB,wBAAA;EACA,2BAAA;;AAGD,WAAY;EACX,wBAAA;EACA,2BAAA;;AAGD,YAAa,MAAK;EACjB,wBAAA;EACA,2BAAA;;AAGD,YAAa,MAAK,KAAM;EACvB,kBAAA;;AAGD,YAAa;EACZ,wBAAA;;AAGD,KAAM;EACL,aAAA;;;AAID,IAAI;AAAQ,GAAG;EACd,cAAA;;AAGD,GAAG;EACF,8BAAA;;;AAID,QAAS;EACR,WAAA;EACA,kBAAA;;;AAID,SAAU,MAAM;AAAG,SAAU;EAC5B,gBAAA;;;AAID;EACC,eAAA;EAEA,2BAAA;;AAHD,KAKC;EACC,qBAAA;EACA,mBAAA;EACA,WAAA;EACA,iBAAA;EACA,SAAA;EACA,gBAAA;EACA,sBAAA;EACA,cAAA;;AAbF,KAgBC,EAAC;EACA,mBAAA;EACA,YAAA;;AAlBF,KAqBC,EAAC;EACA,gBAAA;;AAKF;EACC,kBAAA;;AAGD,cAAc;AAAQ,aAAa;AAAQ,YAAY;EACtD,sBAAA;;AAGD;AAAgB;AAAe;EAC9B,sBAAA;;AAGD;EACC,2BAAA;;AAID,KAAK;EACJ,yBAAA;;AAID,QAAQ;EACP,4BAA4B,wBAA5B;EACA,gBAAA;;AAID,UAAW;EACV,gBAAA;EACA,gBAAA;EACA,kBAAA;EACA,2CAAA;EACA,aAAA;EACA,YAAA;;AAGD,UAAW,MAAK;EACf,UAAA","file":"@layout_popup.css"} {"version":3,"sources":["@layout_popup.less"],"names":[],"mappings":";AACA;EACC,WAAA;;AAGD;EACC,aAAA;;AAGD;EACC,qBAAA;;AAGD,CAAC;AAAW,CAAC,SAAS;AAAQ,CAAC,SAAS;AAAS,IAAI;EACpD,WAAA;;AAGD,CAAC;AAAU,IAAI;AAAU,IAAI;EAC5B,cAAA;;AAGD,IAAI;AAAO,KAAK;AAAO,CAAC;EACvB,sBAAA;;AAGD,CAAC;EACA,iBAAA;;AAGD,IAAI;AAAM,GAAG;EACZ,cAAA;;AAGD,IAAI;EACH,cAAA;;AAGD,GAAG,IAAI;EACN,mBAAmB,8CAAnB;;AAGD;EACC,uBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAM;EACL,sBAAA;;AAGD,MAAO;AAAI,MAAO;EACjB,gBAAA;;AAGD,CAAC;AAAU,GAAG;EACb,yBAAA;EACA,kBAAA;EACA,mBAAA;EACA,qBAAA;;AAGD,CAAC,QAAS;AAAI,GAAG,QAAS;EACzB,6BAAA;;AAGD;EACC,mBAAA;EACA,2BAAA;EACA,gBAAA;EACA,uBAAA;;AAGD,GAAG;AAAS,CAAC;EACZ,eAAA;;;AAID,GAAG;EACF,UAAA;;AAGD,GAAG;EACF,YAAA;;AAGD,GAAG;EACF,UAAA;;AAGD,GAAG;EACF,WAAA;;;AAID,MAAM;EACL,cAAA;;;AAID;EACC,kBAAA;EACA,UAAA;EACA,UAAA;EACA,mBAAA;EACA,kBAAA;EACA,UAAA;;AAGD,mBAAqC;EACpC;IACC,SAAA;;;AAIF,KAAK;EACJ,SAAA;;AAGD,KAAK;EACJ,UAAA;;AAGD,mBAAqC;EACpC,KAAK;IACJ,SAAA;;;AAIF,KAAM,MAAM,GAAE;EACb,WAAA;;AAGD,KAAM,MAAM,GAAE;EACb,WAAA;;AAGD,KAAM,MAAM;EACX,mBAAA;;AAGD,KAAM,GAAE;EACP,8BAAA;;AAGD,KAAM,MAAM,GAAE;EACb,mBAAA;;AAGD,KAAM,MAAM,GAAE;EACb,sBAAA;;AAGD,KAAM,MAAM,GAAE,aAAc;EAC3B,mBAAA;;AAGD,KAAM,MAAM,GAAG;EACd,mBAAA;EACA,kBAAA;EACA,gBAAA;;AAGD,KAAM;EACL,mBAAA;EACA,iBAAA;;AAGD,KAAM,GAAG;EACR,gBAAA;;AAGD,KAAM,GAAG,KAAI;EACZ,cAAA;;AAGD,KAAM,GAAG;EACR,gBAAA;EACA,0BAAA;EACA,UAAA;;AAGD,KAAM,GAAG,EAAC;EACT,SAAS,GAAT;;AAGD,KAAM,GAAG,EAAC;EACT,SAAS,GAAT;;AAGD,KAAM;EACL,mBAAA;;AAGD,KAAM,GAAG,KAAI;EACZ,gBAAA;;AAGD,KAAM,QAAO;EACZ,gBAAA;EACA,cAAA;EACA,gBAAA;;;AAID,KAAK;EACJ,gBAAA;;AAGD,KAAK,KAAK;EACT,UAAA;EACA,WAAA;;;AAID;EACC,wBAAA;;;AAID,iBAAkB;EACjB,gBAAA;;AAGD,iBAAkB,MAAK;EACtB,UAAA;;AAGD,iBAAkB,MAAM;EACvB,2BAAA;;AAGD,MAAM;EACL,sBAAA;;;AAID,mBAAqC;EACpC,OAAO,IAAI;IACV,sBAAA;;;;AAKF,KAAK;EACJ,0BAAA;;AAGD,KAAK;EACJ,cAAA;;;AAOD,WAAY,MAAK;EAChB,wBAAA;EACA,2BAAA;;AAGD,WAAY;EACX,wBAAA;EACA,2BAAA;;AAGD,YAAa,MAAK;EACjB,wBAAA;EACA,2BAAA;;AAGD,YAAa,MAAK,KAAM;EACvB,kBAAA;;AAGD,YAAa;EACZ,wBAAA;;AAGD,KAAM;EACL,aAAA;;;AAID,IAAI;AAAQ,GAAG;EACd,cAAA;;AAGD,GAAG;EACF,8BAAA;;;AAID,QAAS;EACR,WAAA;EACA,kBAAA;;;AAID,SAAU,MAAM;AAAG,SAAU;EAC5B,gBAAA;;;AAID;EACC,eAAA;EAEA,2BAAA;;AAHD,KAKC;EACC,qBAAA;EACA,mBAAA;EACA,WAAA;EACA,iBAAA;EACA,SAAA;EACA,gBAAA;EACA,sBAAA;EACA,cAAA;;AAbF,KAgBC,EAAC;EACA,mBAAA;EACA,YAAA;;AAlBF,KAqBC,EAAC;EACA,gBAAA;;AAKF;EACC,kBAAA;;AAGD,cAAc;AAAQ,aAAa;AAAQ,YAAY;EACtD,sBAAA;;AAGD;AAAgB;AAAe;EAC9B,sBAAA;;AAGD;EACC,2BAAA;;AAID,KAAK;EACJ,yBAAA;;AAID,QAAQ;EACP,4BAA4B,wBAA5B;EACA,gBAAA;;AAID,UAAW;EACV,gBAAA;EACA,gBAAA;EACA,kBAAA;EACA,2CAAA;EACA,aAAA;EACA,YAAA;;AAGD,UAAW,MAAK;EACf,UAAA","file":"@layout_popup.css"}

View File

@@ -63,6 +63,7 @@ p.comment, div.comment {
color: rgba(0, 0, 0, 0.4); color: rgba(0, 0, 0, 0.4);
padding-top: 0.4em; padding-top: 0.4em;
font-weight: normal; font-weight: normal;
word-break: break-all;
} }
p.comment em, div.comment em { p.comment em, div.comment em {

View File

@@ -47,27 +47,30 @@
<tr v-if="node.records.length > 0"> <tr v-if="node.records.length > 0">
<td>DNS记录</td> <td>DNS记录</td>
<td> <td>
<table class="ui table celled"> <div :style="{opacity: dnsIsExcludingLnNode ? 0.5 : 1.0}">
<thead class="full-width"> <table class="ui table celled">
<tr> <thead class="full-width">
<th>记录名</th> <tr>
<th>记录类型</th> <th>记录</th>
<th>线路</th> <th>记录类型</th>
<th>记录值</th> <th>线路</th>
</tr> <th>记录值</th>
</thead> </tr>
</thead>
<tr v-for="record in node.records"> <tr v-for="record in node.records">
<td>{{record.name}}</td> <td>{{record.name}}</td>
<td>{{record.type}}</td> <td>{{record.type}}</td>
<td> <td>
<span v-if="record.route.length > 0">{{record.route}}</span> <span v-if="record.route.length > 0">{{record.route}}</span>
<span v-else class="disabled">默认</span> <span v-else class="disabled">默认</span>
</td> </td>
<td>{{record.value}}</td> <td>{{record.value}}</td>
</tr> </tr>
</table> </table>
<p class="comment">通过设置A记录可以将集群上的服务请求转发到不同线路的节点上。</p> <p class="comment" v-if="!dnsIsExcludingLnNode">通过设置A记录可以将集群上的服务请求转发到不同线路的节点上。</p>
</div>
<p class="comment" v-if="dnsIsExcludingLnNode"><span class="red">当前集群DNS已设置不解析Ln节点所以当前节点不会加入DNS解析如需加入请修改"集群设置" -- "DNS设置" -- "更多选项" -- "包含Ln节点"。</span></p>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -88,6 +91,10 @@
<td>级别</td> <td>级别</td>
<td> <td>
<span :class="{blue: node.levelInfo.code > 1}">{{node.levelInfo.name}}</span> <span :class="{blue: node.levelInfo.code > 1}">{{node.levelInfo.name}}</span>
&nbsp; &nbsp; <span v-if="node.level > 1 && node.lnAddrs != null && node.lnAddrs.length > 0">
<span v-for="lnAddr in node.lnAddrs" class="ui label tiny basic">{{lnAddr}}</span>
</span>
<p class="comment" v-if="node.level > 1 && node.lnAddrs != null && node.lnAddrs.length > 0">指定了Ln节点访问地址。</p>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@@ -48,10 +48,17 @@
<td colspan="2"><more-options-indicator></more-options-indicator></td> <td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr> </tr>
<tbody v-show="moreOptionsVisible"> <tbody v-show="moreOptionsVisible">
<tr v-if="teaIsPlus && canUpdateLevel"> <tr v-show="teaIsPlus && canUpdateLevel">
<td>级别</td> <td>级别</td>
<td> <td>
<node-level-selector :v-node-level="node.level"></node-level-selector> <node-level-selector :v-node-level="node.level" @change="changeLevel"></node-level-selector>
</td>
</tr>
<tr v-show="teaIsPlus && canUpdateLevel && nodeLevel > 1">
<td>L2级别访问地址</td>
<td>
<values-box name="lnAddrs" :v-values="node.lnAddrs" placeholder="IP地址"></values-box>
<p class="comment">如果不为空边缘节点访问当前L2节点时将会使用这些IP地址如果没有设置将会使用当前节点已经填写的IP地址。</p>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@@ -26,4 +26,12 @@ Tea.context(function () {
this.updateClusters = function () { this.updateClusters = function () {
this.showClustersBox = !this.showClustersBox this.showClustersBox = !this.showClustersBox
} }
/**
* 级别相关
*/
this.nodeLevel = this.node.level
this.changeLevel = function (level) {
this.nodeLevel = level
}
}) })

View File

@@ -52,6 +52,13 @@
<p class="comment">选中后表示允许使用CNAME直接访问网站服务如果取消选中则表示CNAME只作为DNS解析记录使用。</p> <p class="comment">选中后表示允许使用CNAME直接访问网站服务如果取消选中则表示CNAME只作为DNS解析记录使用。</p>
</td> </td>
</tr> </tr>
<tr v-show="teaIsPlus">
<td>包含Ln节点</td>
<td>
<checkbox name="includingLnNodes" v-model="includingLnNodes"></checkbox>
<p class="comment">选中后表示域名解析中包含L2及以上级别节点。</p>
</td>
</tr>
<tr> <tr>
<td>记录TTL</td> <td>记录TTL</td>
<td> <td>

View File

@@ -52,6 +52,7 @@
<second-menu v-if="logs.length > 0"> <second-menu v-if="logs.length > 0">
<a href="" class="item" @click.prevent="updatePageRead()">[本页已读]</a> <a href="" class="item" @click.prevent="updatePageRead()">[本页已读]</a>
<a href="" class="item" @click.prevent="updateAllRead()">[全部已读]</a> <a href="" class="item" @click.prevent="updateAllRead()">[全部已读]</a>
<a href="" class="item" @click.prevent="updateNodeRead(firstUnreadNode.id)" v-if="firstUnreadNode != null">["{{firstUnreadNode.name}}"节点已读]</a>
</second-menu> </second-menu>
</div> </div>

View File

@@ -27,6 +27,16 @@ Tea.context(function () {
}) })
} }
this.updateNodeRead = function (nodeId) {
this.$post(".readLogs")
.params({
nodeId: nodeId
})
.success(function () {
teaweb.reload()
})
}
this.updateAllRead = function () { this.updateAllRead = function () {
this.$post(".readAllLogs") this.$post(".readAllLogs")
.params({}) .params({})

View File

@@ -55,6 +55,20 @@
<td colspan="2"><more-options-indicator></more-options-indicator></td> <td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr> </tr>
<tbody v-show="moreOptionsVisible"> <tbody v-show="moreOptionsVisible">
<tr>
<td>允许通过CNAME访问网站服务</td>
<td>
<checkbox name="cnameAsDomain" v-model="cnameAsDomain"></checkbox>
<p class="comment">选中后表示允许使用CNAME直接访问网站服务如果取消选中则表示CNAME只作为DNS解析记录使用。</p>
</td>
</tr>
<tr v-show="teaIsPlus">
<td>包含Ln节点</td>
<td>
<checkbox name="includingLnNodes" v-model="includingLnNodes"></checkbox>
<p class="comment">选中后表示域名解析中包含L2及以上级别节点。</p>
</td>
</tr>
<tr> <tr>
<td>记录TTL</td> <td>记录TTL</td>
<td> <td>

View File

@@ -43,14 +43,14 @@
<td class="color-border">缓存文件句柄缓存</td> <td class="color-border">缓存文件句柄缓存</td>
<td> <td>
<input type="text" name="fileOpenFileCacheMax" maxlength="6" value="0" style="width: 10em"/> <input type="text" name="fileOpenFileCacheMax" maxlength="6" value="0" style="width: 10em"/>
<p class="comment">保持在内存中的缓存文件句柄的数量,提升缓存文件打开速度,建议数量不超过缓存文件数量的十分之一。</p> <p class="comment"><pro-warning-label></pro-warning-label>保持在内存中的缓存文件句柄的数量,提升缓存文件打开速度,建议数量不超过缓存文件数量的十分之一。</p>
</td> </td>
</tr> </tr>
<tr v-show="teaIsPlus"> <tr v-show="teaIsPlus">
<td class="color-border">开启Sendfile</td> <td class="color-border">开启Sendfile</td>
<td> <td>
<checkbox name="fileEnableSendfile"></checkbox> <checkbox name="fileEnableSendfile"></checkbox>
<p class="comment"><plus-label></plus-label>使用sendfile提升发送缓存文件的效率。</p> <p class="comment"><pro-warning-label></pro-warning-label><plus-label></plus-label>使用sendfile提升发送缓存文件的效率。</p>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -52,14 +52,14 @@
<td class="color-border">缓存文件句柄缓存</td> <td class="color-border">缓存文件句柄缓存</td>
<td> <td>
<input type="text" name="fileOpenFileCacheMax" v-model="fileOpenFileCacheMax" maxlength="6" value="0" style="width: 10em"/> <input type="text" name="fileOpenFileCacheMax" v-model="fileOpenFileCacheMax" maxlength="6" value="0" style="width: 10em"/>
<p class="comment">保持在内存中的缓存文件句柄的数量,提升缓存文件打开速度,建议数量不超过缓存文件数量的十分之一。</p> <p class="comment"><pro-warning-label></pro-warning-label>保持在内存中的缓存文件句柄的数量,提升缓存文件打开速度,建议数量不超过缓存文件数量的十分之一。</p>
</td> </td>
</tr> </tr>
<tr v-show="teaIsPlus"> <tr v-show="teaIsPlus">
<td class="color-border">开启Sendfile</td> <td class="color-border">开启Sendfile</td>
<td> <td>
<checkbox name="fileEnableSendfile" v-model="cachePolicy.options.enableSendfile"></checkbox> <checkbox name="fileEnableSendfile" v-model="cachePolicy.options.enableSendfile"></checkbox>
<p class="comment"><plus-label></plus-label>使用sendfile提升发送缓存文件的效率。</p> <p class="comment"><pro-warning-label></pro-warning-label><plus-label></plus-label>使用sendfile提升发送缓存文件的效率。</p>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

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

View File

@@ -2,9 +2,9 @@
{$template "/datepicker"} {$template "/datepicker"}
<first-menu> <first-menu>
<menu-item :href="path + '?clusterId=' + clusterId + '&nodeId=' + nodeId + '&serverId=' + serverId + '&day=' + day + '&keyword=' + keyword + '&ip=' + ip + '&domain=' + domain + '&hour=' + hour + '&pageSize=' + pageSize" :active="hasError == 0 && hasWAF == 0">所有日志</menu-item> <menu-item :href="path + '?' + query('')" :active="hasError == 0 && hasWAF == 0">所有日志</menu-item>
<menu-item :href="path + '?clusterId=' + clusterId + '&nodeId=' + nodeId + '&serverId=' + serverId + '&day=' + day + '&hasError=1' + '&keyword=' + keyword + '&ip=' + ip + '&domain=' + domain + '&hour=' + hour + '&pageSize=' + pageSize" :active="hasError > 0">错误日志</menu-item> <menu-item :href="path + '?' + query('hasError=1')" :active="hasError > 0">错误日志</menu-item>
<menu-item :href="path + '?clusterId=' + clusterId + '&nodeId=' + nodeId + '&serverId=' + serverId + '&day=' + day + '&hasWAF=1' + '&keyword=' + keyword + '&ip=' + ip + '&domain=' + domain + '&hour=' + hour + '&pageSize=' + pageSize" :active="hasWAF > 0">WAF日志</menu-item> <menu-item :href="path + '?' + query('hasWAF=1')" :active="hasWAF > 0">WAF日志</menu-item>
<span class="item disabled">|</span> <span class="item disabled">|</span>
<menu-item :href="'/servers/logs/settings'" code="settings">设置</menu-item> <menu-item :href="'/servers/logs/settings'" code="settings">设置</menu-item>
</first-menu> </first-menu>
@@ -29,7 +29,7 @@
</http-access-log-search-box> </http-access-log-search-box>
</form> </form>
<http-access-log-partitions-box :v-partition="partition" :v-day="day"></http-access-log-partitions-box> <http-access-log-partitions-box :v-partition="partition" :v-day="day" :v-query="currentQuery"></http-access-log-partitions-box>
<warning-message v-if="isSlowQuery">看起来你的访问日志查询非常慢({{slowQueryCost}}s建议<span v-if="domain.length == 0"> 1指定具体域名查询2</span>通过添加新的 <a href="/db">[日志节点]</a> 来分散存储访问日志。</warning-message> <warning-message v-if="isSlowQuery">看起来你的访问日志查询非常慢({{slowQueryCost}}s建议<span v-if="domain.length == 0"> 1指定具体域名查询2</span>通过添加新的 <a href="/db">[日志节点]</a> 来分散存储访问日志。</warning-message>
@@ -43,10 +43,10 @@
</table> </table>
<div v-if="accessLogs.length > 0"> <div v-if="accessLogs.length > 0">
<a :href="path + '?clusterId=' + clusterId + '&nodeId=' + nodeId + '&serverId=' + serverId + '&requestId=' + lastRequestId + '&day=' + day + '&hasError=' + hasError + '&hasWAF=' + hasWAF + '&keyword=' + keyword + '&ip=' + ip + '&domain=' + domain + '&hour=' + hour + '&pageSize=' + pageSize + '&partition=' + partition" v-if="hasPrev">上一页</a> <a :href="path + '?' + query('requestId=' + lastRequestId + '&hasError=' + hasError + '&hasWAF=' + hasWAF + '&partition=' + partition)" v-if="hasPrev">上一页</a>
<span v-else class="disabled">上一页</span> <span v-else class="disabled">上一页</span>
<span class="disabled">&nbsp; | &nbsp;</span> <span class="disabled">&nbsp; | &nbsp;</span>
<a :href="path + '?clusterId=' + clusterId + '&nodeId=' + nodeId + '&serverId=' + serverId + '&requestId=' + nextRequestId + '&day=' + day + '&hasError=' + hasError + '&hasWAF=' + hasWAF + '&keyword=' + keyword + '&ip=' + ip + '&domain=' + domain + '&hour=' + hour + '&pageSize=' + pageSize + '&partition=' + partition" v-if="hasMore">下一页</a> <a :href="path + '?' + query('requestId=' + nextRequestId + '&hasError=' + hasError + '&hasWAF=' + hasWAF + '&partition=' + partition)" v-if="hasMore">下一页</a>
<span v-else class="disabled">下一页</span> <span v-else class="disabled">下一页</span>
<page-size-selector></page-size-selector> <page-size-selector></page-size-selector>

View File

@@ -19,4 +19,60 @@ Tea.context(function () {
accessLog.wafInfo = null accessLog.wafInfo = null
} }
}) })
this.query = function (args) {
// 初始化时页面尚未设置Vue变量所以使用全局的变量获取
let that = TEA.ACTION.data
if (that.clusterId == null) {
that.clusterId = 0
}
if (that.nodeId == null) {
that.nodeId = 0
}
if (that.serverId == null) {
that.serverId = 0
}
if (that.day == null) {
that.day = ""
}
if (that.keyword == null) {
that.keyword = ""
}
if (that.ip == null) {
that.ip = ""
}
if (that.domain == null) {
that.domain = ""
}
if (that.hour == null) {
that.hour = ""
}
if (that.pageSize == null) {
that.pageSize = 0
}
let query = 'clusterId=' + that.clusterId + '&nodeId=' + that.nodeId + '&serverId=' + that.serverId + '&day=' + that.day + '&keyword=' + encodeURIComponent(that.keyword) + '&ip=' + that.ip + '&domain=' + that.domain + '&hour=' + that.hour + '&pageSize=' + that.pageSize
if (args != null && args.length > 0) {
query += "&" + args
}
return query
}
this.allQuery = function () {
if (this.query == null) {
// 尚未初始化完成
return
}
let query = this.query()
if (this.hasError == 1) {
query += "&hasError=1"
}
if (this.hasWAF == 1) {
query += "&hasWAF=1"
}
return query
}
this.currentQuery = this.allQuery()
}) })

View File

@@ -15,9 +15,9 @@
{$template "/left_menu_with_menu"} {$template "/left_menu_with_menu"}
<div class="right-box with-menu"> <div class="right-box with-menu">
<first-menu> <first-menu>
<menu-item :href="path + '?serverId=' + serverId + '&day=' + day + '&keyword=' + keyword + '&ip=' + ip + '&domain=' + domain + '&hour=' + hour + '&pageSize=' + pageSize" :active="hasError == 0 && hasWAF == 0">所有日志</menu-item> <menu-item :href="path + '?' + query()" :active="hasError == 0 && hasWAF == 0">所有日志</menu-item>
<menu-item :href="path + '?serverId=' + serverId + '&day=' + day + '&hasError=1' + '&keyword=' + keyword + '&ip=' + ip + '&domain=' + domain + '&hour=' + hour + '&pageSize=' + pageSize" :active="hasError > 0">错误日志</menu-item> <menu-item :href="path + '?' + query('hasError=1')" :active="hasError > 0">错误日志</menu-item>
<menu-item :href="path + '?serverId=' + serverId + '&day=' + day + '&hasWAF=1' + '&keyword=' + keyword + '&ip=' + ip + '&domain=' + domain + '&hour=' + hour + '&pageSize=' + pageSize" :active="hasWAF > 0">WAF日志</menu-item> <menu-item :href="path + '?' + query('hasWAF=1')" :active="hasWAF > 0">WAF日志</menu-item>
</first-menu> </first-menu>
<form method="get" class="ui form small" :action="path"> <form method="get" class="ui form small" :action="path">
@@ -38,7 +38,7 @@
</form> </form>
<!-- 分区 --> <!-- 分区 -->
<http-access-log-partitions-box :v-day="day" :v-partition="partition"></http-access-log-partitions-box> <http-access-log-partitions-box :v-day="day" :v-partition="partition" :v-query="currentQuery"></http-access-log-partitions-box>
<p class="comment" v-if="accessLogs.length == 0">暂时还没有访问日志。</p> <p class="comment" v-if="accessLogs.length == 0">暂时还没有访问日志。</p>
@@ -50,10 +50,10 @@
</table> </table>
<div v-if="accessLogs.length > 0"> <div v-if="accessLogs.length > 0">
<a :href="path + '?serverId=' + serverId + '&requestId=' + lastRequestId + '&day=' + day + '&hasError=' + hasError + '&hasWAF=' + hasWAF + '&keyword=' + keyword + '&ip=' + ip + '&domain=' + domain + '&hour=' + hour + '&pageSize=' + pageSize + '&partition=' + partition" v-if="hasPrev">上一页</a> <a :href="path + '?' + query('requestId=' + lastRequestId + '&hasError=' + hasError + '&hasWAF=' + hasWAF + '&partition=' + partition)" v-if="hasPrev">上一页</a>
<span v-else class="disabled">上一页</span> <span v-else class="disabled">上一页</span>
<span class="disabled">&nbsp; | &nbsp;</span> <span class="disabled">&nbsp; | &nbsp;</span>
<a :href="path + '?serverId=' + serverId + '&requestId=' + nextRequestId + '&day=' + day + '&hasError=' + hasError + '&hasWAF=' + hasWAF + '&keyword=' + keyword + '&ip=' + ip + '&domain=' + domain + '&hour=' + hour + '&pageSize=' + pageSize + '&partition=' + partition" v-if="hasMore">下一页</a> <a :href="path + '?' + query('requestId=' + nextRequestId + '&hasError=' + hasError + '&hasWAF=' + hasWAF + '&partition=' + partition)" v-if="hasMore">下一页</a>
<span v-else class="disabled">下一页</span> <span v-else class="disabled">下一页</span>
<page-size-selector></page-size-selector> <page-size-selector></page-size-selector>
</div> </div>

View File

@@ -1,22 +1,71 @@
Tea.context(function () { Tea.context(function () {
this.$delay(function () { this.$delay(function () {
let that = this let that = this
teaweb.datepicker("day-input", function (day) { teaweb.datepicker("day-input", function (day) {
that.day = day that.day = day
}) })
}) })
let that = this let that = this
this.accessLogs.forEach(function (accessLog) { this.accessLogs.forEach(function (accessLog) {
if (typeof (that.regions[accessLog.remoteAddr]) == "string") { if (typeof (that.regions[accessLog.remoteAddr]) == "string") {
accessLog.region = that.regions[accessLog.remoteAddr] accessLog.region = that.regions[accessLog.remoteAddr]
} else { } else {
accessLog.region = "" accessLog.region = ""
} }
if (accessLog.firewallRuleSetId > 0 && typeof (that.wafInfos[accessLog.firewallRuleSetId]) == "object") { if (accessLog.firewallRuleSetId > 0 && typeof (that.wafInfos[accessLog.firewallRuleSetId]) == "object") {
accessLog.wafInfo = that.wafInfos[accessLog.firewallRuleSetId] accessLog.wafInfo = that.wafInfos[accessLog.firewallRuleSetId]
} else { } else {
accessLog.wafInfo = null accessLog.wafInfo = null
} }
}) })
this.query = function (args) {
// 初始化时页面尚未设置Vue变量所以使用全局的变量获取
let that = TEA.ACTION.data
if (that.serverId == null) {
that.serverId = 0
}
if (that.keyword == null) {
that.keyword = ""
}
if (that.ip == null) {
that.ip = ""
}
if (that.domain == null) {
that.domain = ""
}
if (that.pageSize == null) {
that.pageSize = ""
}
if (that.day == null) {
that.day = ""
}
if (that.hour == null) {
that.hour = ""
}
let query = 'serverId=' + that.serverId + '&day=' + that.day + '&keyword=' + encodeURIComponent(that.keyword) + '&ip=' + that.ip + '&domain=' + that.domain + '&hour=' + that.hour + '&pageSize=' + that.pageSize
if (args != null && args.length > 0) {
query += "&" + args
}
return query
}
this.allQuery = function () {
if (this.query == null) {
// 尚未初始化完成
return
}
let query = this.query()
if (this.hasError == 1) {
query += "&hasError=1"
}
if (this.hasWAF == 1) {
query += "&hasWAF=1"
}
return query
}
this.currentQuery = this.allQuery()
}) })

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