diff --git a/internal/web/actions/default/servers/logs/index.go b/internal/web/actions/default/servers/logs/index.go index 41e646de..b08b8ed9 100644 --- a/internal/web/actions/default/servers/logs/index.go +++ b/internal/web/actions/default/servers/logs/index.go @@ -7,6 +7,7 @@ import ( "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/iwind/TeaGo/lists" + "github.com/iwind/TeaGo/maps" timeutil "github.com/iwind/TeaGo/utils/time" "regexp" "strings" @@ -22,12 +23,14 @@ func (this *IndexAction) Init() { } func (this *IndexAction) RunGet(params struct { - Day string - Keyword string - Ip string - Domain string - HasError int - HasWAF int + ClusterId int64 + NodeId int64 + Day string + Keyword string + Ip string + Domain string + HasError int + HasWAF int RequestId string ServerId int64 @@ -38,6 +41,8 @@ func (this *IndexAction) RunGet(params struct { params.Day = timeutil.Format("Y-m-d") } + this.Data["clusterId"] = params.ClusterId + this.Data["nodeId"] = params.NodeId this.Data["serverId"] = 0 this.Data["path"] = this.Request.URL.Path this.Data["day"] = params.Day @@ -66,6 +71,8 @@ func (this *IndexAction) RunGet(params struct { var before = time.Now() resp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{ RequestId: params.RequestId, + NodeClusterId: params.ClusterId, + NodeId: params.NodeId, ServerId: params.ServerId, HasError: params.HasError > 0, HasFirewallPolicy: params.HasWAF > 0, @@ -108,6 +115,8 @@ func (this *IndexAction) RunGet(params struct { this.Data["hasPrev"] = true prevResp, err := this.RPC().HTTPAccessLogRPC().ListHTTPAccessLogs(this.AdminContext(), &pb.ListHTTPAccessLogsRequest{ RequestId: params.RequestId, + NodeClusterId: params.ClusterId, + NodeId: params.NodeId, ServerId: params.ServerId, HasError: params.HasError > 0, HasFirewallPolicy: params.HasWAF > 0, @@ -144,5 +153,20 @@ func (this *IndexAction) RunGet(params struct { } this.Data["regions"] = regionMap + // 集群列表 + var clusterMaps = []maps.Map{} + clusterResp, err := this.RPC().NodeClusterRPC().FindAllEnabledNodeClusters(this.AdminContext(), &pb.FindAllEnabledNodeClustersRequest{}) + if err != nil { + this.ErrorPage(err) + return + } + for _, cluster := range clusterResp.NodeClusters { + clusterMaps = append(clusterMaps, maps.Map{ + "id": cluster.Id, + "name": cluster.Name, + }) + } + this.Data["clusters"] = clusterMaps + this.Show() } diff --git a/internal/web/actions/default/servers/logs/init.go b/internal/web/actions/default/servers/logs/init.go index e504e224..0760cfaa 100644 --- a/internal/web/actions/default/servers/logs/init.go +++ b/internal/web/actions/default/servers/logs/init.go @@ -17,6 +17,7 @@ func init() { Prefix("/servers/logs"). Get("", new(IndexAction)). GetPost("/settings", new(SettingsAction)). + Post("/nodeOptions", new(NodeOptionsAction)). EndAll() }) } diff --git a/internal/web/actions/default/servers/logs/nodeOptions.go b/internal/web/actions/default/servers/logs/nodeOptions.go new file mode 100644 index 00000000..84f33caf --- /dev/null +++ b/internal/web/actions/default/servers/logs/nodeOptions.go @@ -0,0 +1,34 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + +package logs + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/maps" +) + +type NodeOptionsAction struct { + actionutils.ParentAction +} + +func (this *NodeOptionsAction) RunPost(params struct { + ClusterId int64 +}) { + resp, err := this.RPC().NodeRPC().FindAllEnabledNodesWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesWithNodeClusterIdRequest{NodeClusterId: params.ClusterId}) + if err != nil { + this.ErrorPage(err) + return + } + + var nodeMaps = []maps.Map{} + for _, node := range resp.Nodes { + nodeMaps = append(nodeMaps, maps.Map{ + "id": node.Id, + "name": node.Name, + }) + } + this.Data["nodes"] = nodeMaps + + this.Success() +} diff --git a/web/public/js/components.js b/web/public/js/components.js index 65f839ab..3bebc773 100755 --- a/web/public/js/components.js +++ b/web/public/js/components.js @@ -2257,7 +2257,7 @@ Vue.component("http-firewall-rule-label", { }, template: `
| 状态码 |
@@ -8520,6 +8560,9 @@ Vue.component("http-firewall-rules-box", {
| {{paramFilter.code}} {{rule.operator}} {{rule.value}}
+
+ ({{rule.description}})
+
@@ -9824,6 +9867,99 @@ Vue.component("traffic-limit-config-box", {
`
})
+Vue.component("firewall-syn-flood-config-box", {
+ props: ["v-syn-flood-config"],
+ data: function () {
+ let config = this.vSynFloodConfig
+ if (config == null) {
+ config = {
+ isOn: false,
+ minAttempts: 10,
+ timeoutSeconds: 600,
+ ignoreLocal: true
+ }
+ }
+ return {
+ config: config,
+ isEditing: false,
+ minAttempts: config.minAttempts,
+ timeoutSeconds: config.timeoutSeconds
+ }
+ },
+ methods: {
+ edit: function () {
+ this.isEditing = !this.isEditing
+ }
+ },
+ watch: {
+ minAttempts: function (v) {
+ let count = parseInt(v)
+ if (isNaN(count)) {
+ count = 10
+ }
+ if (count < 3) {
+ count = 3
+ }
+ this.config.minAttempts = count
+ },
+ timeoutSeconds: function (v) {
+ let seconds = parseInt(v)
+ if (isNaN(seconds)) {
+ seconds = 10
+ }
+ if (seconds < 60) {
+ seconds = 60
+ }
+ this.config.timeoutSeconds = seconds
+ }
+ },
+ template: `
+
+
+
+ 已启用 / 空连接次数:{{config.minAttempts}}次/分钟 / 封禁时间:{{config.timeoutSeconds}}秒 / 忽略局域网访问
+
+ 未启用
+
+
+
+ `
+})
+
// TODO 支持关键词搜索
// TODO 改成弹窗选择
Vue.component("admin-selector", {
@@ -10091,6 +10227,9 @@ Vue.component("ip-list-table", {
{{item.sourcePolicy.name}} » {{item.sourceGroup.name}} » {{item.sourceSet.name}}
{{item.sourcePolicy.name}} » {{item.sourceGroup.name}} » {{item.sourceSet.name}}
+
|
日志
@@ -11468,6 +11607,143 @@ Vue.component("request-variables-describer", {
})
+Vue.component("combo-box", {
+ props: ["name", "title", "placeholder", "size", "v-items", "v-value"],
+ data: function () {
+ let items = this.vItems
+ if (items == null || !(items instanceof Array)) {
+ items = []
+ }
+
+ // 自动使用ID作为值
+ items.forEach(function (v) {
+ if (v.value == null) {
+ v.value = v.id
+ }
+ })
+
+ // 当前选中项
+ let selectedItem = null
+ if (this.vValue != null) {
+ let that = this
+ items.forEach(function (v) {
+ if (v.value == that.vValue) {
+ selectedItem = v
+ }
+ })
+ }
+
+ return {
+ allItems: items,
+ items: items.$copy(),
+ selectedItem: selectedItem,
+ keyword: "",
+ visible: false,
+ hideTimer: null,
+ hoverIndex: 0
+ }
+ },
+ methods: {
+ reset: function () {
+ this.selectedItem = null
+ this.change()
+ this.hoverIndex = 0
+
+ let that = this
+ setTimeout(function () {
+ if (that.$refs.searchBox) {
+ that.$refs.searchBox.focus()
+ }
+ })
+ },
+ changeKeyword: function () {
+ this.hoverIndex = 0
+ let keyword = this.keyword
+ if (keyword.length == 0) {
+ this.items = this.allItems.$copy()
+ return
+ }
+ this.items = this.allItems.$copy().filter(function (v) {
+ return teaweb.match(v.name, keyword)
+ })
+ },
+ selectItem: function (item) {
+ this.selectedItem = item
+ this.change()
+ this.hoverIndex = 0
+ this.keyword = ""
+ this.changeKeyword()
+ },
+ confirm: function () {
+ if (this.items.length > this.hoverIndex) {
+ this.selectItem(this.items[this.hoverIndex])
+ }
+ },
+ show: function () {
+ this.visible = true
+
+ // 不要重置hoverIndex,以便焦点可以在输入框和可选项之间切换
+ },
+ hide: function () {
+ let that = this
+ this.hideTimer = setTimeout(function () {
+ that.visible = false
+ }, 500)
+ },
+ downItem: function () {
+ this.hoverIndex++
+ if (this.hoverIndex > this.items.length - 1) {
+ this.hoverIndex = 0
+ }
+ this.focusItem()
+ },
+ upItem: function () {
+ this.hoverIndex--
+ if (this.hoverIndex < 0) {
+ this.hoverIndex = 0
+ }
+ this.focusItem()
+ },
+ focusItem: function () {
+ if (this.hoverIndex < this.items.length) {
+ this.$refs.itemRef[this.hoverIndex].focus()
+ let that = this
+ setTimeout(function () {
+ that.$refs.searchBox.focus()
+ if (that.hideTimer != null) {
+ clearTimeout(that.hideTimer)
+ that.hideTimer = null
+ }
+ })
+ }
+ },
+ change: function () {
+ this.$emit("change", this.selectedItem)
+ }
+ },
+ template: ``
+})
+
Vue.component("time-duration-box", {
props: ["v-name", "v-value", "v-count", "v-unit"],
mounted: function () {
diff --git a/web/public/js/components/common/combo-box.js b/web/public/js/components/common/combo-box.js
new file mode 100644
index 00000000..902b9f9d
--- /dev/null
+++ b/web/public/js/components/common/combo-box.js
@@ -0,0 +1,136 @@
+Vue.component("combo-box", {
+ props: ["name", "title", "placeholder", "size", "v-items", "v-value"],
+ data: function () {
+ let items = this.vItems
+ if (items == null || !(items instanceof Array)) {
+ items = []
+ }
+
+ // 自动使用ID作为值
+ items.forEach(function (v) {
+ if (v.value == null) {
+ v.value = v.id
+ }
+ })
+
+ // 当前选中项
+ let selectedItem = null
+ if (this.vValue != null) {
+ let that = this
+ items.forEach(function (v) {
+ if (v.value == that.vValue) {
+ selectedItem = v
+ }
+ })
+ }
+
+ return {
+ allItems: items,
+ items: items.$copy(),
+ selectedItem: selectedItem,
+ keyword: "",
+ visible: false,
+ hideTimer: null,
+ hoverIndex: 0
+ }
+ },
+ methods: {
+ reset: function () {
+ this.selectedItem = null
+ this.change()
+ this.hoverIndex = 0
+
+ let that = this
+ setTimeout(function () {
+ if (that.$refs.searchBox) {
+ that.$refs.searchBox.focus()
+ }
+ })
+ },
+ changeKeyword: function () {
+ this.hoverIndex = 0
+ let keyword = this.keyword
+ if (keyword.length == 0) {
+ this.items = this.allItems.$copy()
+ return
+ }
+ this.items = this.allItems.$copy().filter(function (v) {
+ return teaweb.match(v.name, keyword)
+ })
+ },
+ selectItem: function (item) {
+ this.selectedItem = item
+ this.change()
+ this.hoverIndex = 0
+ this.keyword = ""
+ this.changeKeyword()
+ },
+ confirm: function () {
+ if (this.items.length > this.hoverIndex) {
+ this.selectItem(this.items[this.hoverIndex])
+ }
+ },
+ show: function () {
+ this.visible = true
+
+ // 不要重置hoverIndex,以便焦点可以在输入框和可选项之间切换
+ },
+ hide: function () {
+ let that = this
+ this.hideTimer = setTimeout(function () {
+ that.visible = false
+ }, 500)
+ },
+ downItem: function () {
+ this.hoverIndex++
+ if (this.hoverIndex > this.items.length - 1) {
+ this.hoverIndex = 0
+ }
+ this.focusItem()
+ },
+ upItem: function () {
+ this.hoverIndex--
+ if (this.hoverIndex < 0) {
+ this.hoverIndex = 0
+ }
+ this.focusItem()
+ },
+ focusItem: function () {
+ if (this.hoverIndex < this.items.length) {
+ this.$refs.itemRef[this.hoverIndex].focus()
+ let that = this
+ setTimeout(function () {
+ that.$refs.searchBox.focus()
+ if (that.hideTimer != null) {
+ clearTimeout(that.hideTimer)
+ that.hideTimer = null
+ }
+ })
+ }
+ },
+ change: function () {
+ this.$emit("change", this.selectedItem)
+ }
+ },
+ template: ``
+})
\ No newline at end of file
diff --git a/web/public/js/components/server/http-access-log-search-box.js b/web/public/js/components/server/http-access-log-search-box.js
index 35a3be1e..6c59bf08 100644
--- a/web/public/js/components/server/http-access-log-search-box.js
+++ b/web/public/js/components/server/http-access-log-search-box.js
@@ -1,6 +1,13 @@
// 访问日志搜索框
Vue.component("http-access-log-search-box", {
- props: ["v-ip", "v-domain", "v-keyword"],
+ props: ["v-ip", "v-domain", "v-keyword", "v-cluster-id", "v-node-id", "v-clusters"],
+ mounted: function () {
+ if (this.vClusterId >0) {
+ this.changeCluster({
+ value: this.vClusterId
+ })
+ }
+ },
data: function () {
let ip = this.vIp
if (ip == null) {
@@ -20,7 +27,8 @@ Vue.component("http-access-log-search-box", {
return {
ip: ip,
domain: domain,
- keyword: keyword
+ keyword: keyword,
+ nodes: []
}
},
methods: {
@@ -52,9 +60,23 @@ Vue.component("http-access-log-search-box", {
parent.submit()
}, 500)
}
+ },
+ changeCluster: function (item) {
+ this.nodes = []
+ if (item != null) {
+ let that = this
+ Tea.action("/servers/logs/nodeOptions")
+ .params({
+ clusterId: item.value
+ })
+ .post()
+ .success(function (resp) {
+ that.nodes = resp.data.nodes
+ })
+ }
}
},
- template: `
+ template: ` `
diff --git a/web/views/@default/servers/logs/index.html b/web/views/@default/servers/logs/index.html
index f334f4c9..670c476b 100644
--- a/web/views/@default/servers/logs/index.html
+++ b/web/views/@default/servers/logs/index.html
@@ -2,9 +2,9 @@
{$template "/datepicker"}
@@ -79,8 +101,16 @@ Vue.component("http-access-log-search-box", {
+
+
+
+
-
+
|