Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f738ba9152 | ||
|
|
95c27119ac | ||
|
|
2b2b516788 | ||
|
|
0bb63b5bb7 | ||
|
|
48012072d7 | ||
|
|
0053949bc0 | ||
|
|
88e7246366 | ||
|
|
fbedf3b605 | ||
|
|
d5659e627d | ||
|
|
709d4dc805 | ||
|
|
37f052e9a5 | ||
|
|
58c765e97f | ||
|
|
da332c2756 | ||
|
|
e9d329bf8b |
1
build/licenses/README.md
Normal file
1
build/licenses/README.md
Normal file
@@ -0,0 +1 @@
|
||||
这个目录下我们列举了所有需要公开声明的第三方License,如果有遗漏,烦请告知 iwind.liu@gmail.com。再次感谢这些开源软件项目和贡献人员!
|
||||
30
build/licenses/miekg-dns.md
Normal file
30
build/licenses/miekg-dns.md
Normal file
@@ -0,0 +1,30 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
As this is fork of the official Go code the same license applies.
|
||||
Extensions of the original work are copyright (c) 2011 Miek Gieben
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "0.1.0"
|
||||
Version = "0.1.1"
|
||||
|
||||
ProductName = "Edge Admin"
|
||||
ProcessName = "edge-admin"
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// RPC客户端
|
||||
// RPCClient RPC客户端
|
||||
type RPCClient struct {
|
||||
apiConfig *configs.APIConfig
|
||||
conns []*grpc.ClientConn
|
||||
@@ -30,7 +30,7 @@ type RPCClient struct {
|
||||
locker sync.Mutex
|
||||
}
|
||||
|
||||
// 构造新的RPC客户端
|
||||
// NewRPCClient 构造新的RPC客户端
|
||||
func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
|
||||
if apiConfig == nil {
|
||||
return nil, errors.New("api config should not be nil")
|
||||
|
||||
@@ -37,7 +37,7 @@ func TestRPC_Dial_HTTP(t *testing.T) {
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
}{
|
||||
Endpoints: []string{"127.0.0.1:8003"},
|
||||
Endpoints: []string{"http://127.0.0.1:8004"},
|
||||
},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
@@ -58,7 +58,7 @@ func TestRPC_Dial_HTTP_2(t *testing.T) {
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
}{
|
||||
Endpoints: []string{"http://127.0.0.1:8003"},
|
||||
Endpoints: []string{"https://127.0.0.1:8003"},
|
||||
},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
|
||||
@@ -17,13 +17,25 @@ func (this *LogsAction) Init() {
|
||||
}
|
||||
|
||||
func (this *LogsAction) RunGet(params struct {
|
||||
NodeId int64
|
||||
NodeId int64
|
||||
DayFrom string
|
||||
DayTo string
|
||||
Keyword string
|
||||
Level string
|
||||
}) {
|
||||
this.Data["nodeId"] = params.NodeId
|
||||
this.Data["dayFrom"] = params.DayFrom
|
||||
this.Data["dayTo"] = params.DayTo
|
||||
this.Data["keyword"] = params.Keyword
|
||||
this.Data["level"] = params.Level
|
||||
|
||||
countResp, err := this.RPC().NodeLogRPC().CountNodeLogs(this.AdminContext(), &pb.CountNodeLogsRequest{
|
||||
Role: "node",
|
||||
NodeId: params.NodeId,
|
||||
Role: "node",
|
||||
NodeId: params.NodeId,
|
||||
DayFrom: params.DayFrom,
|
||||
DayTo: params.DayTo,
|
||||
Keyword: params.Keyword,
|
||||
Level: params.Level,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -33,10 +45,14 @@ func (this *LogsAction) RunGet(params struct {
|
||||
page := this.NewPage(count, 20)
|
||||
|
||||
logsResp, err := this.RPC().NodeLogRPC().ListNodeLogs(this.AdminContext(), &pb.ListNodeLogsRequest{
|
||||
NodeId: params.NodeId,
|
||||
Role: "node",
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
NodeId: params.NodeId,
|
||||
Role: "node",
|
||||
DayFrom: params.DayFrom,
|
||||
DayTo: params.DayTo,
|
||||
Keyword: params.Keyword,
|
||||
Level: params.Level,
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
|
||||
logs := []maps.Map{}
|
||||
@@ -47,6 +63,7 @@ func (this *LogsAction) RunGet(params struct {
|
||||
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", log.CreatedAt),
|
||||
"level": log.Level,
|
||||
"isToday": timeutil.FormatTime("Y-m-d", log.CreatedAt) == timeutil.Format("Y-m-d"),
|
||||
"count": log.Count,
|
||||
})
|
||||
}
|
||||
this.Data["logs"] = logs
|
||||
|
||||
@@ -55,8 +55,8 @@ func (this *TrafficInAction) RunPost(params struct {
|
||||
if ok {
|
||||
result = append(result, maps.Map{
|
||||
"label": timeutil.FormatTime("H:i", timestamp),
|
||||
"value": total,
|
||||
"text": numberutils.FormatBytes(total),
|
||||
"value": total / 60,
|
||||
"text": numberutils.FormatBytes(total / 60),
|
||||
})
|
||||
} else {
|
||||
result = append(result, maps.Map{
|
||||
|
||||
@@ -55,8 +55,8 @@ func (this *TrafficOutAction) RunPost(params struct {
|
||||
if ok {
|
||||
result = append(result, maps.Map{
|
||||
"label": timeutil.FormatTime("H:i", timestamp),
|
||||
"value": total,
|
||||
"text": numberutils.FormatBytes(total),
|
||||
"value": total / 60,
|
||||
"text": numberutils.FormatBytes(total / 60),
|
||||
})
|
||||
} else {
|
||||
result = append(result, maps.Map{
|
||||
|
||||
@@ -15,8 +15,14 @@ func (this *IndexAction) Init() {
|
||||
this.Nav("", "grant", "index")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
countResp, err := this.RPC().NodeGrantRPC().CountAllEnabledNodeGrants(this.AdminContext(), &pb.CountAllEnabledNodeGrantsRequest{})
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
Keyword string
|
||||
}) {
|
||||
this.Data["keyword"] = params.Keyword
|
||||
|
||||
countResp, err := this.RPC().NodeGrantRPC().CountAllEnabledNodeGrants(this.AdminContext(), &pb.CountAllEnabledNodeGrantsRequest{
|
||||
Keyword: params.Keyword,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
@@ -25,8 +31,9 @@ func (this *IndexAction) RunGet(params struct{}) {
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
grantsResp, err := this.RPC().NodeGrantRPC().ListEnabledNodeGrants(this.AdminContext(), &pb.ListEnabledNodeGrantsRequest{
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
Keyword: params.Keyword,
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -57,6 +64,7 @@ func (this *IndexAction) RunGet(params struct{}) {
|
||||
"type": grant.Method,
|
||||
"name": grantutils.FindGrantMethodName(grant.Method),
|
||||
},
|
||||
"username": grant.Username,
|
||||
"countClusters": countClusters,
|
||||
"countNodes": countNodes,
|
||||
})
|
||||
|
||||
@@ -27,10 +27,12 @@ func (this *SelectPopupAction) RunGet(params struct{}) {
|
||||
grantMaps := []maps.Map{}
|
||||
for _, grant := range grants {
|
||||
grantMaps = append(grantMaps, maps.Map{
|
||||
"id": grant.Id,
|
||||
"name": grant.Name,
|
||||
"method": grant.Method,
|
||||
"methodName": grantutils.FindGrantMethodName(grant.Method),
|
||||
"id": grant.Id,
|
||||
"name": grant.Name,
|
||||
"method": grant.Method,
|
||||
"methodName": grantutils.FindGrantMethodName(grant.Method),
|
||||
"username": grant.Username,
|
||||
"description": grant.Description,
|
||||
})
|
||||
}
|
||||
this.Data["grants"] = grantMaps
|
||||
|
||||
87
internal/web/actions/default/clusters/logs/index.go
Normal file
87
internal/web/actions/default/clusters/logs/index.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
DayFrom string
|
||||
DayTo string
|
||||
Keyword string
|
||||
Level string
|
||||
}) {
|
||||
this.Data["dayFrom"] = params.DayFrom
|
||||
this.Data["dayTo"] = params.DayTo
|
||||
this.Data["keyword"] = params.Keyword
|
||||
this.Data["level"] = params.Level
|
||||
|
||||
countResp, err := this.RPC().NodeLogRPC().CountNodeLogs(this.AdminContext(), &pb.CountNodeLogsRequest{
|
||||
NodeId: 0,
|
||||
Role: "node",
|
||||
DayFrom: params.DayFrom,
|
||||
DayTo: params.DayTo,
|
||||
Keyword: params.Keyword,
|
||||
Level: params.Level,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
count := countResp.Count
|
||||
page := this.NewPage(count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
logsResp, err := this.RPC().NodeLogRPC().ListNodeLogs(this.AdminContext(), &pb.ListNodeLogsRequest{
|
||||
NodeId: 0,
|
||||
Role: "node",
|
||||
DayFrom: params.DayFrom,
|
||||
DayTo: params.DayTo,
|
||||
Keyword: params.Keyword,
|
||||
Level: params.Level,
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
|
||||
logs := []maps.Map{}
|
||||
for _, log := range logsResp.NodeLogs {
|
||||
// 节点信息
|
||||
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: log.NodeId})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
node := nodeResp.Node
|
||||
if node == nil || node.NodeCluster == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
logs = append(logs, maps.Map{
|
||||
"tag": log.Tag,
|
||||
"description": log.Description,
|
||||
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", log.CreatedAt),
|
||||
"level": log.Level,
|
||||
"isToday": timeutil.FormatTime("Y-m-d", log.CreatedAt) == timeutil.Format("Y-m-d"),
|
||||
"count": log.Count,
|
||||
"node": maps.Map{
|
||||
"id": node.Id,
|
||||
"cluster": maps.Map{
|
||||
"id": node.NodeCluster.Id,
|
||||
"name": node.NodeCluster.Name,
|
||||
},
|
||||
"name": node.Name,
|
||||
},
|
||||
})
|
||||
}
|
||||
this.Data["logs"] = logs
|
||||
|
||||
this.Show()
|
||||
}
|
||||
19
internal/web/actions/default/clusters/logs/init.go
Normal file
19
internal/web/actions/default/clusters/logs/init.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNode)).
|
||||
Data("teaMenu", "clusters").
|
||||
Data("teaSubMenu", "log").
|
||||
Prefix("/clusters/logs").
|
||||
Get("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -6,10 +6,12 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MessageResult 和节点消息通讯结果定义
|
||||
type MessageResult struct {
|
||||
NodeId int64 `json:"nodeId"`
|
||||
NodeName string `json:"nodeName"`
|
||||
@@ -17,7 +19,7 @@ type MessageResult struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// 向集群发送命令消息
|
||||
// SendMessageToCluster 向集群发送命令消息
|
||||
func SendMessageToCluster(ctx context.Context, clusterId int64, code string, msg interface{}, timeoutSeconds int32) (results []*MessageResult, err error) {
|
||||
results = []*MessageResult{}
|
||||
|
||||
@@ -153,10 +155,17 @@ func SendMessageToCluster(ctx context.Context, clusterId int64, code string, msg
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// 对结果进行排序
|
||||
if len(results) > 0 {
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].NodeId < results[j].NodeId
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 向一组节点发送命令消息
|
||||
// SendMessageToNodeIds 向一组节点发送命令消息
|
||||
func SendMessageToNodeIds(ctx context.Context, nodeIds []int64, code string, msg interface{}, timeoutSeconds int32) (results []*MessageResult, err error) {
|
||||
results = []*MessageResult{}
|
||||
if len(nodeIds) == 0 {
|
||||
@@ -321,5 +330,12 @@ func SendMessageToNodeIds(ctx context.Context, nodeIds []int64, code string, msg
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// 对结果进行排序
|
||||
if len(results) > 0 {
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].NodeId < results[j].NodeId
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// 选择证书
|
||||
// SelectPopupAction 选择证书
|
||||
type SelectPopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
@@ -25,10 +25,12 @@ func (this *SelectPopupAction) Init() {
|
||||
func (this *SelectPopupAction) RunGet(params struct {
|
||||
ViewSize string
|
||||
SelectedCertIds string
|
||||
Keyword string
|
||||
}) {
|
||||
// TODO 支持关键词搜索
|
||||
// TODO 列出常用和最新的证书供用户选择
|
||||
|
||||
this.Data["keyword"] = params.Keyword
|
||||
|
||||
// 已经选择的证书
|
||||
selectedCertIds := []string{}
|
||||
if len(params.SelectedCertIds) > 0 {
|
||||
@@ -40,7 +42,9 @@ func (this *SelectPopupAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["viewSize"] = params.ViewSize
|
||||
|
||||
countResp, err := this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{})
|
||||
countResp, err := this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
|
||||
Keyword: params.Keyword,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
@@ -50,8 +54,9 @@ func (this *SelectPopupAction) RunGet(params struct {
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
listResp, err := this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
Keyword: params.Keyword,
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
|
||||
certConfigs := []*sslconfigs.SSLCertConfig{}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net/http"
|
||||
"strconv"
|
||||
strings "strings"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PurgeAction struct {
|
||||
@@ -53,6 +53,7 @@ func (this *PurgeAction) RunGet(params struct {
|
||||
func (this *PurgeAction) RunPost(params struct {
|
||||
CachePolicyId int64
|
||||
ClusterId int64
|
||||
Type string
|
||||
Keys string
|
||||
Must *actions.Must
|
||||
}) {
|
||||
@@ -96,6 +97,11 @@ func (this *PurgeAction) RunPost(params struct {
|
||||
CachePolicyJSON: cachePolicyJSON,
|
||||
Keys: realKeys,
|
||||
}
|
||||
if params.Type == "prefix" {
|
||||
msg.Type = messageconfigs.PurgeCacheMessageTypeDir
|
||||
} else {
|
||||
msg.Type = messageconfigs.PurgeCacheMessageTypeFile
|
||||
}
|
||||
results, err := nodeutils.SendMessageToCluster(this.AdminContext(), params.ClusterId, messageconfigs.MessageCodePurgeCache, msg, 10)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -63,8 +63,13 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
Description string
|
||||
IsOn bool
|
||||
|
||||
RefsJSON []byte
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
// 创建日志
|
||||
defer this.CreateLog(oplogs.LevelInfo, "修改缓存策略:%d", params.CachePolicyId)
|
||||
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入策略名称")
|
||||
@@ -104,6 +109,22 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 校验缓存条件
|
||||
refs := []*serverconfigs.HTTPCacheRef{}
|
||||
if len(params.RefsJSON) > 0 {
|
||||
err = json.Unmarshal(params.RefsJSON, &refs)
|
||||
if err != nil {
|
||||
this.Fail("缓存条件解析失败:" + err.Error())
|
||||
}
|
||||
for _, ref := range refs {
|
||||
err = ref.Init()
|
||||
if err != nil {
|
||||
this.Fail("缓存条件校验失败:" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = this.RPC().HTTPCachePolicyRPC().UpdateHTTPCachePolicy(this.AdminContext(), &pb.UpdateHTTPCachePolicyRequest{
|
||||
HttpCachePolicyId: params.CachePolicyId,
|
||||
IsOn: params.IsOn,
|
||||
@@ -120,8 +141,15 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
// 创建日志
|
||||
defer this.CreateLog(oplogs.LevelInfo, "修改缓存策略:%d", params.CachePolicyId)
|
||||
// 修改缓存条件
|
||||
_, err = this.RPC().HTTPCachePolicyRPC().UpdateHTTPCachePolicyRefs(this.AdminContext(), &pb.UpdateHTTPCachePolicyRefsRequest{
|
||||
HttpCachePolicyId: params.CachePolicyId,
|
||||
RefsJSON: params.RefsJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
24
internal/web/actions/default/servers/fixLog.go
Normal file
24
internal/web/actions/default/servers/fixLog.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package servers
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type FixLogAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *FixLogAction) RunPost(params struct {
|
||||
LogId int64
|
||||
}) {
|
||||
_, err := this.RPC().NodeLogRPC().FixNodeLog(this.AdminContext(), &pb.FixNodeLogRequest{NodeLogId: params.LogId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -4,9 +4,11 @@ import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
@@ -250,5 +252,48 @@ func (this *IndexAction) RunGet(params struct {
|
||||
// 是否有用户管理权限
|
||||
this.Data["canVisitUser"] = configloaders.AllowModule(this.AdminId(), configloaders.AdminModuleCodeUser)
|
||||
|
||||
// 显示服务相关的日志
|
||||
errorLogsResp, err := this.RPC().NodeLogRPC().ListNodeLogs(this.AdminContext(), &pb.ListNodeLogsRequest{
|
||||
NodeId: 0,
|
||||
Role: "node",
|
||||
Offset: 0,
|
||||
Size: 10,
|
||||
Level: "error",
|
||||
FixedState: int32(configutils.BoolStateNo),
|
||||
AllServers: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
errorLogMaps := []maps.Map{}
|
||||
for _, errorLog := range errorLogsResp.NodeLogs {
|
||||
serverResp, err := this.RPC().ServerRPC().FindEnabledUserServerBasic(this.AdminContext(), &pb.FindEnabledUserServerBasicRequest{ServerId: errorLog.ServerId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
server := serverResp.Server
|
||||
if server == nil {
|
||||
// 设置为已修复
|
||||
_, err = this.RPC().NodeLogRPC().FixNodeLog(this.AdminContext(), &pb.FixNodeLogRequest{NodeLogId: errorLog.Id})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
errorLogMaps = append(errorLogMaps, maps.Map{
|
||||
"id": errorLog.Id,
|
||||
"description": errorLog.Description,
|
||||
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", errorLog.CreatedAt),
|
||||
"serverId": errorLog.ServerId,
|
||||
"serverName": server.Name,
|
||||
})
|
||||
}
|
||||
this.Data["errorLogs"] = errorLogMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ func init() {
|
||||
Get("", new(IndexAction)).
|
||||
GetPost("/create", new(CreateAction)).
|
||||
GetPost("/update", new(UpdateAction)).
|
||||
Post("/fixLog", new(FixLogAction)).
|
||||
|
||||
GetPost("/addPortPopup", new(AddPortPopupAction)).
|
||||
GetPost("/addServerNamePopup", new(AddServerNamePopupAction)).
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
@@ -76,6 +78,24 @@ func (this *IndexAction) RunPost(params struct {
|
||||
this.Fail("端口地址解析失败:" + err.Error())
|
||||
}
|
||||
|
||||
// 检查端口地址是否正确
|
||||
for _, addr := range addresses {
|
||||
err = addr.Init()
|
||||
if err != nil {
|
||||
this.Fail("绑定端口校验失败:" + err.Error())
|
||||
}
|
||||
|
||||
if regexp.MustCompile(`^\d+$`).MatchString(addr.PortRange) {
|
||||
port := types.Int(addr.PortRange)
|
||||
if port > 65535 {
|
||||
this.Fail("绑定的端口地址不能大于65535")
|
||||
}
|
||||
if port == 443 {
|
||||
this.Fail("端口443通常是HTTPS的端口,不能用在HTTP上")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server, _, isOk := serverutils.FindServer(this.Parent(), params.ServerId)
|
||||
if !isOk {
|
||||
return
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
@@ -87,7 +88,23 @@ func (this *IndexAction) RunPost(params struct {
|
||||
this.Fail("端口地址解析失败:" + err.Error())
|
||||
}
|
||||
|
||||
// TODO 校验addresses
|
||||
// 检查端口地址是否正确
|
||||
for _, addr := range addresses {
|
||||
err = addr.Init()
|
||||
if err != nil {
|
||||
this.Fail("绑定端口校验失败:" + err.Error())
|
||||
}
|
||||
|
||||
if regexp.MustCompile(`^\d+$`).MatchString(addr.PortRange) {
|
||||
port := types.Int(addr.PortRange)
|
||||
if port > 65535 {
|
||||
this.Fail("绑定的端口地址不能大于65535")
|
||||
}
|
||||
if port == 80 {
|
||||
this.Fail("端口80通常是HTTP的端口,不能用在HTTPS上")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 校验SSL
|
||||
var sslPolicyId = int64(0)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type CreatePopupAction struct {
|
||||
@@ -27,6 +28,7 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
BeforeURL string
|
||||
AfterURL string
|
||||
MatchPrefix bool
|
||||
MatchRegexp bool
|
||||
KeepRequestURI bool
|
||||
|
||||
Status int
|
||||
@@ -39,7 +41,12 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
Require("请填写跳转前的URL")
|
||||
|
||||
// 校验格式
|
||||
{
|
||||
if params.MatchRegexp {
|
||||
_, err := regexp.Compile(params.BeforeURL)
|
||||
if err != nil {
|
||||
this.Fail("跳转前URL正则表达式错误:" + err.Error())
|
||||
}
|
||||
} else {
|
||||
u, err := url.Parse(params.BeforeURL)
|
||||
if err != nil {
|
||||
this.FailField("beforeURL", "请输入正确的跳转前URL")
|
||||
@@ -56,7 +63,9 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
Require("请填写跳转后URL")
|
||||
|
||||
// 校验格式
|
||||
{
|
||||
if params.MatchRegexp {
|
||||
// 正则表达式情况下不做校验
|
||||
} else {
|
||||
u, err := url.Parse(params.AfterURL)
|
||||
if err != nil {
|
||||
this.FailField("afterURL", "请输入正确的跳转后URL")
|
||||
@@ -76,6 +85,7 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
"beforeURL": params.BeforeURL,
|
||||
"afterURL": params.AfterURL,
|
||||
"matchPrefix": params.MatchPrefix,
|
||||
"matchRegexp": params.MatchRegexp,
|
||||
"keepRequestURI": params.KeepRequestURI,
|
||||
"isOn": true,
|
||||
}
|
||||
|
||||
@@ -181,6 +181,11 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
|
||||
"subtitle": "集群列表",
|
||||
"icon": "cloud",
|
||||
"subItems": []maps.Map{
|
||||
{
|
||||
"name": "节点日志",
|
||||
"url": "/clusters/logs",
|
||||
"code": "log",
|
||||
},
|
||||
{
|
||||
"name": "SSH认证",
|
||||
"url": "/clusters/grants",
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/logs"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/regions"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/regions/items"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/tasks"
|
||||
|
||||
58
web/public/js/components/server/http-cache-refs-box.js
Normal file
58
web/public/js/components/server/http-cache-refs-box.js
Normal file
@@ -0,0 +1,58 @@
|
||||
Vue.component("http-cache-refs-box", {
|
||||
props: ["v-cache-refs"],
|
||||
data: function () {
|
||||
let refs = this.vCacheRefs
|
||||
if (refs == null) {
|
||||
refs = []
|
||||
}
|
||||
return {
|
||||
refs: refs
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
timeUnitName: function (unit) {
|
||||
switch (unit) {
|
||||
case "ms":
|
||||
return "毫秒"
|
||||
case "second":
|
||||
return "秒"
|
||||
case "minute":
|
||||
return "分钟"
|
||||
case "hour":
|
||||
return "小时"
|
||||
case "day":
|
||||
return "天"
|
||||
case "week":
|
||||
return "周 "
|
||||
}
|
||||
return unit
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="refsJSON" :value="JSON.stringify(refs)"/>
|
||||
|
||||
<p class="comment" v-if="refs.length == 0">暂时还没有缓存条件。</p>
|
||||
<div v-show="refs.length > 0">
|
||||
<table class="ui table selectable celled">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>缓存条件</th>
|
||||
<th class="width10">缓存时间</th>
|
||||
<th class="two op">操作</th>
|
||||
</tr>
|
||||
<tr v-for="(cacheRef, index) in refs">
|
||||
<td>
|
||||
<http-request-conds-view :v-conds="cacheRef.conds"></http-request-conds-view>
|
||||
</td>
|
||||
<td>{{cacheRef.life.count}} {{timeUnitName(cacheRef.life.unit)}}</td>
|
||||
<td>
|
||||
<a href="" @click.prevent="updateRef(index, cacheRef)">修改</a>
|
||||
<a href="" @click.prevent="removeRef(index)">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<div class="margin"></div>
|
||||
</div>`
|
||||
})
|
||||
105
web/public/js/components/server/http-cache-refs-config-box.js
Normal file
105
web/public/js/components/server/http-cache-refs-config-box.js
Normal file
@@ -0,0 +1,105 @@
|
||||
Vue.component("http-cache-refs-config-box", {
|
||||
props: ["v-cache-refs"],
|
||||
data: function () {
|
||||
let refs = this.vCacheRefs
|
||||
if (refs == null) {
|
||||
refs = []
|
||||
}
|
||||
return {
|
||||
refs: refs
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addRef: function () {
|
||||
window.UPDATING_CACHE_REF = null
|
||||
|
||||
let width = window.innerWidth
|
||||
if (width > 1024) {
|
||||
width = 1024
|
||||
}
|
||||
let height = window.innerHeight
|
||||
if (height > 500) {
|
||||
height = 500
|
||||
}
|
||||
let that = this
|
||||
teaweb.popup("/servers/server/settings/cache/createPopup", {
|
||||
width: width + "px",
|
||||
height: height + "px",
|
||||
callback: function (resp) {
|
||||
that.refs.push(resp.data.cacheRef)
|
||||
}
|
||||
})
|
||||
},
|
||||
updateRef: function (index, cacheRef) {
|
||||
window.UPDATING_CACHE_REF = cacheRef
|
||||
|
||||
let width = window.innerWidth
|
||||
if (width > 1024) {
|
||||
width = 1024
|
||||
}
|
||||
let height = window.innerHeight
|
||||
if (height > 500) {
|
||||
height = 500
|
||||
}
|
||||
let that = this
|
||||
teaweb.popup("/servers/server/settings/cache/createPopup", {
|
||||
width: width + "px",
|
||||
height: height + "px",
|
||||
callback: function (resp) {
|
||||
Vue.set(that.refs, index, resp.data.cacheRef)
|
||||
}
|
||||
})
|
||||
},
|
||||
removeRef: function (index) {
|
||||
let that = this
|
||||
teaweb.confirm("确定要删除此缓存设置吗?", function () {
|
||||
that.refs.$remove(index)
|
||||
})
|
||||
},
|
||||
timeUnitName: function (unit) {
|
||||
switch (unit) {
|
||||
case "ms":
|
||||
return "毫秒"
|
||||
case "second":
|
||||
return "秒"
|
||||
case "minute":
|
||||
return "分钟"
|
||||
case "hour":
|
||||
return "小时"
|
||||
case "day":
|
||||
return "天"
|
||||
case "week":
|
||||
return "周 "
|
||||
}
|
||||
return unit
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<input type="hidden" name="refsJSON" :value="JSON.stringify(refs)"/>
|
||||
|
||||
<div>
|
||||
<table class="ui table selectable celled" v-show="refs.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>缓存条件</th>
|
||||
<th class="width10">缓存时间</th>
|
||||
<th class="two op">操作</th>
|
||||
</tr>
|
||||
<tr v-for="(cacheRef, index) in refs">
|
||||
<td>
|
||||
<http-request-conds-view :v-conds="cacheRef.conds"></http-request-conds-view>
|
||||
</td>
|
||||
<td>{{cacheRef.life.count}} {{timeUnitName(cacheRef.life.unit)}}</td>
|
||||
<td>
|
||||
<a href="" @click.prevent="updateRef(index, cacheRef)">修改</a>
|
||||
<a href="" @click.prevent="removeRef(index)">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
|
||||
<button class="ui button tiny" @click.prevent="addRef">+添加缓存设置</button>
|
||||
</div>
|
||||
<div class="margin"></div>
|
||||
</div>`
|
||||
})
|
||||
@@ -50,7 +50,10 @@ Vue.component("http-host-redirect-box", {
|
||||
<!-- TODO 将来支持排序,并支持isOn切换 -->
|
||||
<div v-if="redirects.length > 0">
|
||||
<div v-for="(redirect, index) in redirects" class="ui label basic small" style="margin-bottom: 0.5em;margin-top: 0.5em">
|
||||
<span v-if="redirect.status > 0">[{{redirect.status}}]</span><span v-if="redirect.matchPrefix">[prefix]</span> {{redirect.beforeURL}} -> {{redirect.afterURL}} <a href="" @click.prevent="update(index, redirect)" title="修改"><i class="icon pencil small"></i></a> <a href="" @click.prevent="remove(index)" title="删除"><i class="icon remove"></i></a>
|
||||
<span v-if="redirect.status > 0" class="small grey">[{{redirect.status}}]</span>
|
||||
<span v-if="redirect.matchPrefix" class="small grey">[匹配前缀]</span>
|
||||
<span v-if="redirect.matchRegexp" class="small grey">[正则匹配]</span>
|
||||
{{redirect.beforeURL}} -> {{redirect.afterURL}} <a href="" @click.prevent="update(index, redirect)" title="修改"><i class="icon pencil small"></i></a> <a href="" @click.prevent="remove(index)" title="删除"><i class="icon remove"></i></a>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,9 @@ Vue.component("origin-list-box", {
|
||||
teaweb.popup("/servers/server/settings/origins/addPopup?originType=primary&" + this.vParams, {
|
||||
height: "24em",
|
||||
callback: function (resp) {
|
||||
window.location.reload()
|
||||
teaweb.success("保存成功", function () {
|
||||
window.location.reload()
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -19,7 +21,9 @@ Vue.component("origin-list-box", {
|
||||
teaweb.popup("/servers/server/settings/origins/addPopup?originType=backup&" + this.vParams, {
|
||||
height: "24em",
|
||||
callback: function (resp) {
|
||||
window.location.reload()
|
||||
teaweb.success("保存成功", function () {
|
||||
window.location.reload()
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -27,7 +31,9 @@ Vue.component("origin-list-box", {
|
||||
teaweb.popup("/servers/server/settings/origins/updatePopup?originType=" + originType + "&" + this.vParams + "&originId=" + originId, {
|
||||
height: "24em",
|
||||
callback: function (resp) {
|
||||
window.location.reload()
|
||||
teaweb.success("保存成功", function () {
|
||||
window.location.reload()
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -37,7 +43,9 @@ Vue.component("origin-list-box", {
|
||||
Tea.action("/servers/server/settings/origins/delete?" + that.vParams + "&originId=" + originId + "&originType=" + originType)
|
||||
.post()
|
||||
.success(function () {
|
||||
window.location.reload()
|
||||
teaweb.success("保存成功", function () {
|
||||
window.location.reload()
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,20 +1,58 @@
|
||||
{$layout}
|
||||
{$template "node_menu"}
|
||||
|
||||
{$template "node_menu"}
|
||||
{$var "header"}
|
||||
<!-- datepicker -->
|
||||
<script type="text/javascript" src="/js/moment.min.js"></script>
|
||||
<script type="text/javascript" src="/js/pikaday.js"></script>
|
||||
<link rel="stylesheet" href="/js/pikaday.css"/>
|
||||
<link rel="stylesheet" href="/js/pikaday.theme.css"/>
|
||||
<link rel="stylesheet" href="/js/pikaday.triangle.css"/>
|
||||
{$end}
|
||||
|
||||
<p class="comment" v-if="logs.length == 0">暂时还没有日志。</p>
|
||||
<form method="get" action="/clusters/cluster/node/logs" class="ui form" autocomplete="off">
|
||||
<input type="hidden" name="clusterId" :value="clusterId"/>
|
||||
<input type="hidden" name="nodeId" :value="nodeId"/>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" name="dayFrom" placeholder="开始日期" v-model="dayFrom" value="" style="width:8em" id="day-from-picker"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<input type="text" name="dayTo" placeholder="结束日期" v-model="dayTo" value="" style="width:8em" id="day-to-picker"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" name="level" v-model="level">
|
||||
<option value="">[级别]</option>
|
||||
<option value="error">错误</option>
|
||||
<option value="warning">警告</option>
|
||||
<option value="info">信息</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<input type="text" name="keyword" style="width:10em" v-model="keyword" placeholder="关键词"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<button type="submit" class="ui button">查询</button>
|
||||
</div>
|
||||
<div class="ui field" v-if="dayFrom.length > 0 || dayTo.length > 0 || keyword.length > 0 || level.length > 0">
|
||||
<a :href="'/clusters/cluster/node/logs?clusterId=' + clusterId + '&nodeId=' + nodeId">[清除条件]</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table class="ui table selectable" v-if="logs.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<p class="comment" v-if="logs.length == 0">暂时还没有日志。</p>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="log in logs">
|
||||
<td>
|
||||
<pre class="log-box"><span :class="{red:log.level == 'error', yellow:log.level == 'warning'}"><span v-if="!log.isToday">[{{log.createdTime}}]</span><strong v-if="log.isToday">[{{log.createdTime}}]</strong>[{{log.tag}}]{{log.description}}</span></pre>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="ui table selectable" v-if="logs.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
<div class="page" v-html="page"></div>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="log in logs">
|
||||
<td>
|
||||
<pre class="log-box"><span :class="{red:log.level == 'error', yellow:log.level == 'warning'}"><span v-if="!log.isToday">[{{log.createdTime}}]</span><strong v-if="log.isToday">[{{log.createdTime}}]</strong>[{{log.tag}}]{{log.description}}</span> <span v-if="log.count > 0" class="ui label tiny red">共{{log.count}}条</span></pre>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="page" v-html="page"></div>
|
||||
6
web/views/@default/clusters/cluster/node/logs.js
Normal file
6
web/views/@default/clusters/cluster/node/logs.js
Normal file
@@ -0,0 +1,6 @@
|
||||
Tea.context(function () {
|
||||
this.$delay(function () {
|
||||
teaweb.datepicker("day-from-picker")
|
||||
teaweb.datepicker("day-to-picker")
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,21 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<form class="ui form" method="get" action="/clusters/grants">
|
||||
<div class="margin"></div>
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" name="keyword" placeholder="名称、用户名等..." v-model="keyword"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<button class="ui button" type="submit">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- SSH认证列表 -->
|
||||
<div class="ui message" v-if="grants.length == 0">暂时还没有认证信息。</div>
|
||||
|
||||
<table class="ui table selectable celled" v-if="grants.length > 0">
|
||||
@@ -8,6 +23,7 @@
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>类型</th>
|
||||
<th>用户名</th>
|
||||
<th class="center width5">集群数</th>
|
||||
<th class="center width5">节点数</th>
|
||||
<th class="two op">操作</th>
|
||||
@@ -18,6 +34,10 @@
|
||||
<td>
|
||||
<span class="ui label tiny basic">{{grant.method.name}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="grant.username.length > 0">{{grant.username}}</span>
|
||||
<span v-else class="disabled">-</span>
|
||||
</td>
|
||||
<td class="center">
|
||||
<span v-if="grant.countClusters > 0">{{grant.countClusters}}</span>
|
||||
<span v-else class="disabled">0</span>
|
||||
|
||||
@@ -2,11 +2,19 @@
|
||||
|
||||
<h3>选择认证</h3>
|
||||
|
||||
<table class="ui table definition">
|
||||
<form class="ui form">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" placeholder="搜索名称、用户名等" v-model="keyword"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td>
|
||||
<span v-if="grants.length == 0">暂时还没有可用的认证。</span>
|
||||
<a class="ui label small basic" v-for="grant in grants" :class="{blue:grantId == grant.id}" @click.prevent="selectGrant(grant)" style="margin-bottom:0.5em">{{grant.name}} <span class="small">({{grant.methodName}})</span></a>
|
||||
<a class="ui label small basic" v-for="grant in grants" :class="{blue:grantId == grant.id}" @click.prevent="selectGrant(grant)" style="margin-bottom:0.5em">{{grant.name}} <span class="small">({{grant.methodName}})</span><span v-if="grant.username.length > 0" class="small">({{grant.username}})</span></a>
|
||||
<p class="comment">请点击选中某个认证。</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
Tea.context(function () {
|
||||
this.grantId = 0;
|
||||
this.grantId = 0
|
||||
this.keyword = ""
|
||||
let allGrants = this.grants.$copy()
|
||||
|
||||
this.selectGrant = function (grant) {
|
||||
NotifyPopup({
|
||||
@@ -8,5 +10,20 @@ Tea.context(function () {
|
||||
grant: grant
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
this.$delay(function () {
|
||||
let that = this
|
||||
this.$watch("keyword", function (keyword) {
|
||||
if (keyword.length > 0) {
|
||||
that.grants = allGrants.$findAll(function (k, grant) {
|
||||
return teaweb.match(grant.name, keyword)
|
||||
|| teaweb.match(grant.description, keyword)
|
||||
|| teaweb.match(grant.username, keyword)
|
||||
})
|
||||
} else {
|
||||
that.grants = allGrants
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
5
web/views/@default/clusters/logs/index.css
Normal file
5
web/views/@default/clusters/logs/index.css
Normal file
@@ -0,0 +1,5 @@
|
||||
pre.log-box {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
/*# sourceMappingURL=index.css.map */
|
||||
1
web/views/@default/clusters/logs/index.css.map
Normal file
1
web/views/@default/clusters/logs/index.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,GAAG;EACF,SAAA;EACA,UAAA","file":"index.css"}
|
||||
62
web/views/@default/clusters/logs/index.html
Normal file
62
web/views/@default/clusters/logs/index.html
Normal file
@@ -0,0 +1,62 @@
|
||||
{$layout}
|
||||
|
||||
{$var "header"}
|
||||
<!-- datepicker -->
|
||||
<script type="text/javascript" src="/js/moment.min.js"></script>
|
||||
<script type="text/javascript" src="/js/pikaday.js"></script>
|
||||
<link rel="stylesheet" href="/js/pikaday.css"/>
|
||||
<link rel="stylesheet" href="/js/pikaday.theme.css"/>
|
||||
<link rel="stylesheet" href="/js/pikaday.triangle.css"/>
|
||||
{$end}
|
||||
|
||||
|
||||
<div class="margin"></div>
|
||||
|
||||
<form method="get" action="/clusters/logs" class="ui form" autocomplete="off">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" name="dayFrom" placeholder="开始日期" v-model="dayFrom" value="" style="width:8em" id="day-from-picker"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<input type="text" name="dayTo" placeholder="结束日期" v-model="dayTo" value="" style="width:8em" id="day-to-picker"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" name="level" v-model="level">
|
||||
<option value="">[级别]</option>
|
||||
<option value="error">错误</option>
|
||||
<option value="warning">警告</option>
|
||||
<option value="info">信息</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<input type="text" name="keyword" style="width:10em" v-model="keyword" placeholder="关键词"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<button type="submit" class="ui button">查询</button>
|
||||
</div>
|
||||
<div class="ui field" v-if="dayFrom.length > 0 || dayTo.length > 0 || keyword.length > 0 || level.length > 0">
|
||||
<a href="/clusters/logs">[清除条件]</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class="comment" v-if="logs.length == 0">暂时还没有日志。</p>
|
||||
|
||||
<table class="ui table selectable celled" v-if="logs.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>集群</th>
|
||||
<th>节点</th>
|
||||
<th>信息</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="log in logs">
|
||||
<td nowrap=""><link-icon :href="'/clusters/cluster?clusterId=' + log.node.cluster.id">{{log.node.cluster.name}}</link-icon></td>
|
||||
<td nowrap=""><link-icon :href="'/clusters/cluster/node?clusterId=' + log.node.cluster.id + '&nodeId=' + log.node.id">{{log.node.name}}</link-icon></td>
|
||||
<td>
|
||||
<pre class="log-box"><span :class="{red:log.level == 'error', yellow:log.level == 'warning'}"><span v-if="!log.isToday">[{{log.createdTime}}]</span><strong v-if="log.isToday">[{{log.createdTime}}]</strong>[{{log.tag}}]{{log.description}}</span> <span v-if="log.count > 0" class="ui label tiny red">共{{log.count}}条</span></pre>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="page" v-html="page"></div>
|
||||
6
web/views/@default/clusters/logs/index.js
Normal file
6
web/views/@default/clusters/logs/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
Tea.context(function () {
|
||||
this.$delay(function () {
|
||||
teaweb.datepicker("day-from-picker")
|
||||
teaweb.datepicker("day-to-picker")
|
||||
})
|
||||
})
|
||||
4
web/views/@default/clusters/logs/index.less
Normal file
4
web/views/@default/clusters/logs/index.less
Normal file
@@ -0,0 +1,4 @@
|
||||
pre.log-box {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -1,6 +1,20 @@
|
||||
{$layout "layout_popup"}
|
||||
|
||||
<h3>选择证书</h3>
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<form class="ui form" action="/servers/certs/selectPopup">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<input type="text" name="keyword" v-model="keyword" placeholder="域名、说明文字等"/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<button class="ui button" type="submit">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- 证书列表 -->
|
||||
<p class="comment" v-if="certs.length == 0">暂时还没有相关的证书。</p>
|
||||
<table class="ui table selectable celled" v-if="certs.length > 0">
|
||||
<thead>
|
||||
|
||||
@@ -80,6 +80,12 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<!-- 默认缓存条件 -->
|
||||
<h4>默认缓存条件</h4>
|
||||
<http-cache-refs-box :v-cache-refs="cachePolicy.cacheRefs"></http-cache-refs-box>
|
||||
|
||||
<!-- 使用此策略的集群 -->
|
||||
<h4>使用此策略的集群</h4>
|
||||
<p class="comment" v-if="clusters.length == 0">暂时还没有集群使用此策略。</p>
|
||||
<table class="ui table selectable" v-if="clusters.length > 0">
|
||||
|
||||
@@ -1,37 +1,47 @@
|
||||
{$layout}
|
||||
{$template "policy_menu"}
|
||||
|
||||
{$template "policy_menu"}
|
||||
<h3>选择集群</h3>
|
||||
<select class="ui dropdown auto-width" v-model="clusterId">
|
||||
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
|
||||
</select>
|
||||
<div class="ui divider"></div>
|
||||
|
||||
<h3>选择集群</h3>
|
||||
<select class="ui dropdown auto-width" v-model="clusterId">
|
||||
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
|
||||
</select>
|
||||
<div class="ui divider"></div>
|
||||
|
||||
<h3>批量删除</h3>
|
||||
<p class="comment">可以批量删除一组Key。</p>
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-before="before" data-tea-success="success" data-tea-fail="fail" data-tea-done="done">
|
||||
<input type="hidden" name="cachePolicyId" :value="cachePolicyId"/>
|
||||
<input type="hidden" name="clusterId" :value="clusterId"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td>Key列表</td>
|
||||
<td>
|
||||
<textarea name="keys" rows="10" ref="focus"></textarea>
|
||||
<p class="comment">每行一个Key。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">操作结果</td>
|
||||
<td>
|
||||
<div v-if="isRequesting">数据发送中...</div>
|
||||
<span class="red" v-if="!isRequesting && !isOk && message.length > 0">失败:{{message}}</span>
|
||||
<div v-if="!isRequesting && isOk">
|
||||
<span v-if="results.length == 0" class="red">此集群下没有任何可用的节点。</span>
|
||||
<div class="ui label tiny" v-for="one in results" :class="{green:one.isOk, red:!one.isOk}" style="margin-bottom: 0.5em">{{one.nodeName}}:{{one.message}}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn v-if="!isRequesting">提交</submit-btn>
|
||||
</form>
|
||||
<h3>批量删除</h3>
|
||||
<p class="comment">可以批量删除一组Key。</p>
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-before="before" data-tea-success="success" data-tea-fail="fail" data-tea-done="done">
|
||||
<input type="hidden" name="cachePolicyId" :value="cachePolicyId"/>
|
||||
<input type="hidden" name="clusterId" :value="clusterId"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">操作类型</td>
|
||||
<td>
|
||||
<radio name="type" :v-value="'key'" v-model="type">根据Key</radio>
|
||||
<radio name="type" :v-value="'prefix'" v-model="type">根据前缀</radio>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span v-if="type == 'key'">Key列表</span>
|
||||
<span v-if="type == 'prefix'">Key前缀列表</span>
|
||||
</td>
|
||||
<td>
|
||||
<textarea name="keys" rows="10" ref="focus"></textarea>
|
||||
<p class="comment" v-if="type == 'key'">每行一个Key,比如是一个完整的URL<code-label>https://example.com/hello/world.html</code-label>。</p>
|
||||
<p class="comment" v-if="type == 'prefix'">每行一个Key前缀,比如是一个URL前缀<code-label>https://example.com/hello/</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>操作结果</td>
|
||||
<td>
|
||||
<div v-if="isRequesting">数据发送中...</div>
|
||||
<span class="red" v-if="!isRequesting && !isOk && message.length > 0">失败:{{message}}</span>
|
||||
<div v-if="!isRequesting && isOk">
|
||||
<span v-if="results.length == 0" class="red">此集群下没有任何可用的节点。</span>
|
||||
<div class="ui label tiny" v-for="one in results" :class="{green:one.isOk, red:!one.isOk}" style="margin-bottom: 0.5em">{{one.nodeName}}:{{one.message}}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn v-if="!isRequesting">提交</submit-btn>
|
||||
</form>
|
||||
@@ -31,4 +31,9 @@ Tea.context(function () {
|
||||
this.done = function () {
|
||||
this.isRequesting = false
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 操作类型
|
||||
*/
|
||||
this.type = "key" // key | prefix
|
||||
})
|
||||
@@ -94,5 +94,9 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h4>默认缓存条件</h4>
|
||||
<http-cache-refs-config-box :v-cache-refs="cachePolicy.cacheRefs"></http-cache-refs-config-box>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
6
web/views/@default/servers/index.css
Normal file
6
web/views/@default/servers/index.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.ui.message .icon {
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: 1.8em;
|
||||
}
|
||||
/*# sourceMappingURL=index.css.map */
|
||||
1
web/views/@default/servers/index.css.map
Normal file
1
web/views/@default/servers/index.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA,GAAG,QACF;EACC,kBAAA;EACA,UAAA;EACA,UAAA","file":"index.css"}
|
||||
@@ -1,6 +1,23 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
|
||||
<!-- 错误日志 -->
|
||||
<div v-if="errorLogs.length > 0">
|
||||
<div class="margin"></div>
|
||||
<div class="ui menu tabular attached">
|
||||
<span class="ui item active"><span class="red">需要修复的错误</span></span>
|
||||
</div>
|
||||
<div class="ui segment attached">
|
||||
<div class="ui message error" v-for="log in errorLogs">
|
||||
[{{log.createdTime}}]
|
||||
<a :href="'/servers/server/settings?serverId=' + log.serverId"><span v-if="log.serverName.length > 0">[{{log.serverName}}]</span><span v-else>[服务]</span></a>
|
||||
{{log.description}}
|
||||
<a href="" title="关闭" @click.prevent="fixLog(log.id)"><i class="ui icon remove small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<form method="get" class="ui form" action="/servers">
|
||||
<input type="hidden" name="auditingFlag" :value="auditingFlag"/>
|
||||
<div class="ui margin"></div>
|
||||
|
||||
@@ -46,4 +46,20 @@ Tea.context(function () {
|
||||
this.showLatest = function () {
|
||||
this.latestVisible = !this.latestVisible
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误日志相关
|
||||
*/
|
||||
this.fixLog = function (logId) {
|
||||
let that = this
|
||||
teaweb.confirm("确定要关闭此错误提示吗?", function () {
|
||||
that.$post(".fixLog")
|
||||
.params({
|
||||
logId: logId
|
||||
})
|
||||
.success(function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
7
web/views/@default/servers/index.less
Normal file
7
web/views/@default/servers/index.less
Normal file
@@ -0,0 +1,7 @@
|
||||
.ui.message {
|
||||
.icon {
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: 1.8em;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
<form class="ui form" data-tea-success="success" data-tea-action="$">
|
||||
<csrf-token></csrf-token>
|
||||
<input type="hidden" name="matchPrefix" :value="redirect.matchPrefix ? 1 : 0"/>
|
||||
<input type="hidden" name="matchRegexp" :value="redirect.matchRegexp ? 1 : 0"/>
|
||||
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
@@ -17,15 +19,17 @@
|
||||
<tr>
|
||||
<td>匹配模式</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="matchPrefix" v-model="matchPrefix">
|
||||
<option value="0">精准匹配</option>
|
||||
<option value="1">匹配前缀</option>
|
||||
<select class="ui dropdown auto-width" name="mode" v-model="mode">
|
||||
<option value="equal">精准匹配</option>
|
||||
<option value="matchPrefix">匹配前缀</option>
|
||||
<option value="matchRegexp">正则匹配</option>
|
||||
</select>
|
||||
<p class="comment" v-if="matchPrefix == 0">精准匹配跳转前的URL。</p>
|
||||
<p class="comment" v-if="matchPrefix == 1">只要URL头部部分包含跳转前URL,即可跳转。</p>
|
||||
<p class="comment" v-if="mode == 'equal'">精准匹配跳转前的URL,即只有访问完全一样的URL才会跳转。</p>
|
||||
<p class="comment" v-if="mode == 'matchPrefix'">只要访问的URL头部部分包含跳转前URL,即可跳转。</p>
|
||||
<p class="comment" v-if="mode == 'matchRegexp'">可以在跳转前URL中使用正则表达式,然后可以在跳转后URL中使用正则表达式中括号的变量,比如<code-label>${1}</code-label>、<code-label>${2}</code-label>等。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="matchPrefix == 1">
|
||||
<tr v-if="mode == 'matchPrefix'">
|
||||
<td>是否保留URL路径参数</td>
|
||||
<td>
|
||||
<checkbox name="keepRequestURI" value="1" v-model="redirect.keepRequestURI"></checkbox>
|
||||
|
||||
@@ -1,17 +1,41 @@
|
||||
Tea.context(function () {
|
||||
this.isCreating = true
|
||||
if (window.parent.UPDATING_REDIRECT != null) {
|
||||
this.isCreating = false
|
||||
this.redirect = window.parent.UPDATING_REDIRECT
|
||||
} else {
|
||||
this.redirect = {
|
||||
status: 0,
|
||||
beforeURL: "",
|
||||
afterURL: "",
|
||||
matchPrefix: false,
|
||||
keepRequestURI: false
|
||||
}
|
||||
}
|
||||
this.isCreating = true
|
||||
if (window.parent.UPDATING_REDIRECT != null) {
|
||||
this.isCreating = false
|
||||
this.redirect = window.parent.UPDATING_REDIRECT
|
||||
} else {
|
||||
this.redirect = {
|
||||
status: 0,
|
||||
beforeURL: "",
|
||||
afterURL: "",
|
||||
matchPrefix: false,
|
||||
matchRegexp: false,
|
||||
keepRequestURI: false
|
||||
}
|
||||
}
|
||||
|
||||
this.matchPrefix = (this.redirect.matchPrefix ? 1 : 0)
|
||||
this.mode = ""
|
||||
if (this.redirect.matchPrefix) {
|
||||
this.mode = "matchPrefix"
|
||||
} else if (this.redirect.matchRegexp) {
|
||||
this.mode = "matchRegexp"
|
||||
} else {
|
||||
this.mode = "equal"
|
||||
}
|
||||
|
||||
this.$delay(function () {
|
||||
let that = this
|
||||
this.$watch("mode", function (v) {
|
||||
if (v == "matchPrefix") {
|
||||
that.redirect.matchPrefix = true
|
||||
that.redirect.matchRegexp = false
|
||||
} else if (v == "matchRegexp") {
|
||||
that.redirect.matchPrefix = false
|
||||
that.redirect.matchRegexp = true
|
||||
} else {
|
||||
that.redirect.matchPrefix = false
|
||||
that.redirect.matchRegexp = false
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user