Compare commits

...

8 Commits

Author SHA1 Message Date
刘祥超
963be2bc63 阶段性提交 2021-06-27 21:59:06 +08:00
刘祥超
bd34a9bd37 服务列表可以搜索端口号 2021-06-25 11:04:45 +08:00
刘祥超
32b93b2bd9 安装时默认设置访问日志保留30天 2021-06-24 21:06:43 +08:00
刘祥超
e7c4507b0b 集群设置左侧菜单显示TOA设置状态 2021-06-24 18:03:33 +08:00
刘祥超
17ae34c44f ACME申请证书时可以设置回调URL 2021-06-24 09:58:44 +08:00
刘祥超
b466ea2fd1 ACME申请证书时可以设置回调URL 2021-06-24 09:27:11 +08:00
刘祥超
8e4ee54f03 实现公用的IP名单 2021-06-23 13:12:33 +08:00
刘祥超
c99547d9e3 变更版本 2021-06-21 14:44:13 +08:00
133 changed files with 3234 additions and 198 deletions

View File

@@ -1,4 +1,5 @@
api.yaml
server.yaml
api_db.yaml
*.pem
*.pem
tip.json

2
go.mod
View File

@@ -9,6 +9,7 @@ require (
github.com/cespare/xxhash v1.1.0
github.com/go-sql-driver/mysql v1.5.0
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f
github.com/miekg/dns v1.1.35
@@ -20,5 +21,6 @@ require (
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect
google.golang.org/grpc v1.38.0
google.golang.org/protobuf v1.26.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
)

View File

@@ -1,7 +1,7 @@
package teaconst
const (
Version = "0.2.3"
Version = "0.2.4"
ProductName = "Edge Admin"
ProcessName = "edge-admin"

View File

@@ -388,6 +388,14 @@ func (this *RPCClient) NSAccessLogRPC() pb.NSAccessLogServiceClient {
return pb.NewNSAccessLogServiceClient(this.pickConn())
}
func (this *RPCClient) MetricItemRPC() pb.MetricItemServiceClient {
return pb.NewMetricItemServiceClient(this.pickConn())
}
func (this *RPCClient) NodeClusterMetricItemRPC() pb.NodeClusterMetricItemServiceClient {
return pb.NewNodeClusterMetricItemServiceClient(this.pickConn())
}
// Context 构造Admin上下文
func (this *RPCClient) Context(adminId int64) context.Context {
ctx := context.Background()

View File

@@ -32,6 +32,9 @@ func (this *RunPopupAction) RunPost(params struct {
this.Fail(err.Error())
}
if resp.Results == nil {
resp.Results = []*pb.ExecuteNodeClusterHealthCheckResponse_Result{}
}
this.Data["results"] = resp.Results
this.Success()
}

View File

@@ -7,6 +7,7 @@ import (
firewallActions "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/firewall-actions"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/health"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/message"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/metrics"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/services"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/thresholds"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/toa"
@@ -67,6 +68,12 @@ func init() {
GetPost("/updatePopup", new(thresholds.UpdatePopupAction)).
Post("/delete", new(thresholds.DeleteAction)).
// 指标
Prefix("/clusters/cluster/settings/metrics").
Get("", new(metrics.IndexAction)).
GetPost("/createPopup", new(metrics.CreatePopupAction)).
Post("/delete", new(metrics.DeleteAction)).
EndAll()
})
}

View File

@@ -0,0 +1,61 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct {
Category string
}) {
if len(params.Category) == 0 {
params.Category = "http"
}
this.Data["category"] = params.Category
countResp, err := this.RPC().MetricItemRPC().CountAllEnabledMetricItems(this.AdminContext(), &pb.CountAllEnabledMetricItemsRequest{Category: params.Category})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
itemsResp, err := this.RPC().MetricItemRPC().ListEnabledMetricItems(this.AdminContext(), &pb.ListEnabledMetricItemsRequest{
Category: params.Category,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var itemMaps = []maps.Map{}
for _, item := range itemsResp.MetricItems {
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"name": item.Name,
"isOn": item.IsOn,
"period": item.Period,
"periodUnit": item.PeriodUnit,
"keys": item.Keys,
"value": item.Value,
"category": item.Category,
})
}
this.Data["items"] = itemMaps
this.Show()
}

View File

@@ -0,0 +1,13 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct{}) {
this.Success()
}

View File

@@ -0,0 +1,51 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "setting")
this.SecondMenu("metric")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
Category string
}) {
if len(params.Category) == 0 {
params.Category = "http"
}
this.Data["category"] = params.Category
itemsResp, err := this.RPC().NodeClusterMetricItemRPC().FindAllNodeClusterMetricItems(this.AdminContext(), &pb.FindAllNodeClusterMetricItemsRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var itemMaps = []maps.Map{}
for _, item := range itemsResp.MetricItems {
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"name": item.Name,
"isOn": item.IsOn,
"period": item.Period,
"periodUnit": item.PeriodUnit,
"keys": item.Keys,
"value": item.Value,
"category": item.Category,
})
}
this.Data["items"] = itemMaps
this.Show()
}

View File

@@ -1,14 +1,12 @@
package clusterutils
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
@@ -38,7 +36,8 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
action.Data["clusterId"] = clusterId
if clusterId > 0 {
cluster, err := dao.SharedNodeClusterDAO.FindEnabledNodeCluster(actionPtr.(rpc.ContextInterface).AdminContext(), clusterId)
var ctx = actionPtr.(rpc.ContextInterface).AdminContext()
cluster, err := dao.SharedNodeClusterDAO.FindEnabledNodeCluster(ctx, clusterId)
if err != nil {
logs.Error(err)
return
@@ -48,6 +47,16 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
return
}
clusterInfo, err := dao.SharedNodeClusterDAO.FindEnabledNodeClusterConfigInfo(ctx, clusterId)
if err != nil {
logs.Error(err)
return
}
if clusterInfo == nil {
action.WriteString("can not find cluster info")
return
}
tabbar := actionutils.NewTabbar()
tabbar.Add("集群列表", "", "/clusters", "", false)
tabbar.Add("集群节点", "", "/clusters/cluster?clusterId="+clusterIdString, "server", selectedTabbar == "node")
@@ -64,7 +73,7 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
secondMenuItem := action.Data.GetString("secondMenuItem")
switch selectedTabbar {
case "setting":
action.Data["leftMenuItems"] = this.createSettingMenu(cluster, secondMenuItem)
action.Data["leftMenuItems"] = this.createSettingMenu(cluster, clusterInfo, secondMenuItem)
}
}
@@ -72,7 +81,7 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
}
// 设置菜单
func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, selectedItem string) (items []maps.Map) {
func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, info *pb.FindEnabledNodeClusterConfigInfoResponse, selectedItem string) (items []maps.Map) {
clusterId := numberutils.FormatInt64(cluster.Id)
items = append(items, maps.Map{
"name": "基础设置",
@@ -92,25 +101,19 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, selectedIt
"isOn": cluster.HttpFirewallPolicyId > 0,
})
{
hasActions, _ := this.checkFirewallActions(cluster.Id)
items = append(items, maps.Map{
"name": "WAF动作",
"url": "/clusters/cluster/settings/firewall-actions?clusterId=" + clusterId,
"isActive": selectedItem == "firewallAction",
"isOn": hasActions,
})
}
items = append(items, maps.Map{
"name": "WAF动作",
"url": "/clusters/cluster/settings/firewall-actions?clusterId=" + clusterId,
"isActive": selectedItem == "firewallAction",
"isOn": info != nil && info.HasFirewallActions,
})
{
healthCheckIsOn, _ := this.checkHealthCheckIsOn(cluster.Id)
items = append(items, maps.Map{
"name": "健康检查",
"url": "/clusters/cluster/settings/health?clusterId=" + clusterId,
"isActive": selectedItem == "health",
"isOn": healthCheckIsOn,
})
}
items = append(items, maps.Map{
"name": "健康检查",
"url": "/clusters/cluster/settings/health?clusterId=" + clusterId,
"isActive": selectedItem == "health",
"isOn": info != nil && info.HealthCheckIsOn,
})
items = append(items, maps.Map{
"name": "DNS设置",
@@ -118,22 +121,26 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, selectedIt
"isActive": selectedItem == "dns",
"isOn": cluster.DnsDomainId > 0 || len(cluster.DnsName) > 0,
})
/**items = append(items, maps.Map{
"name": "统计指标",
"url": "/clusters/cluster/settings/metrics?clusterId=" + clusterId,
"isActive": selectedItem == "metric",
"isOn": info != nil && info.HasMetricItems,
})**/
if teaconst.IsPlus {
hasThresholds, _ := this.checkThresholds(cluster.Id)
items = append(items, maps.Map{
"name": "阈值设置",
"url": "/clusters/cluster/settings/thresholds?clusterId=" + clusterId,
"isActive": selectedItem == "threshold",
"isOn": hasThresholds,
"isOn": info != nil && info.HasThresholds,
})
}
if teaconst.IsPlus {
hasMessageReceivers, _ := this.checkMessages(cluster.Id)
items = append(items, maps.Map{
"name": "消息通知",
"url": "/clusters/cluster/settings/message?clusterId=" + clusterId,
"isActive": selectedItem == "message",
"isOn": hasMessageReceivers,
"isOn": info != nil && info.HasMessageReceivers,
})
}
@@ -148,75 +155,13 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, selectedIt
"url": "/clusters/cluster/settings/services?clusterId=" + clusterId,
"isActive": selectedItem == "service",
})
items = append(items, maps.Map{
"name": "TOA设置",
"url": "/clusters/cluster/settings/toa?clusterId=" + clusterId,
"isActive": selectedItem == "toa",
})
{
items = append(items, maps.Map{
"name": "TOA设置",
"url": "/clusters/cluster/settings/toa?clusterId=" + clusterId,
"isActive": selectedItem == "toa",
"isOn": info != nil && info.IsTOAEnabled,
})
}
return
}
// 检查健康检查是否开启
func (this *ClusterHelper) checkHealthCheckIsOn(clusterId int64) (bool, error) {
rpcClient, err := rpc.SharedRPC()
if err != nil {
return false, err
}
resp, err := rpcClient.NodeClusterRPC().FindNodeClusterHealthCheckConfig(rpcClient.Context(0), &pb.FindNodeClusterHealthCheckConfigRequest{NodeClusterId: clusterId})
if err != nil {
return false, err
}
if len(resp.HealthCheckJSON) > 0 {
healthCheckConfig := &serverconfigs.HealthCheckConfig{}
err = json.Unmarshal(resp.HealthCheckJSON, healthCheckConfig)
if err != nil {
return false, err
}
return healthCheckConfig.IsOn, nil
}
return false, nil
}
// 检查是否有WAF动作
func (this *ClusterHelper) checkFirewallActions(clusterId int64) (bool, error) {
rpcClient, err := rpc.SharedRPC()
if err != nil {
return false, err
}
resp, err := rpcClient.NodeClusterFirewallActionRPC().CountAllEnabledNodeClusterFirewallActions(rpcClient.Context(0), &pb.CountAllEnabledNodeClusterFirewallActionsRequest{NodeClusterId: clusterId})
if err != nil {
return false, err
}
return resp.Count > 0, nil
}
// 检查阈值是否已经设置
func (this *ClusterHelper) checkThresholds(clusterId int64) (bool, error) {
rpcClient, err := rpc.SharedRPC()
if err != nil {
return false, err
}
resp, err := rpcClient.NodeThresholdRPC().CountAllEnabledNodeThresholds(rpcClient.Context(0), &pb.CountAllEnabledNodeThresholdsRequest{
Role: "node",
NodeClusterId: clusterId,
})
if err != nil {
return false, err
}
return resp.Count > 0, nil
}
// 检查消息通知是否已经设置
func (this *ClusterHelper) checkMessages(clusterId int64) (bool, error) {
rpcClient, err := rpc.SharedRPC()
if err != nil {
return false, err
}
resp, err := rpcClient.MessageReceiverRPC().CountAllEnabledMessageReceivers(rpcClient.Context(0), &pb.CountAllEnabledMessageReceiversRequest{
NodeClusterId: clusterId,
})
if err != nil {
return false, err
}
return resp.Count > 0, nil
}

View File

@@ -72,6 +72,7 @@ func (this *CreateAction) RunPost(params struct {
DnsDomain string
Domains []string
AutoRenew bool
AuthURL string
Must *actions.Must
}) {
@@ -123,6 +124,7 @@ func (this *CreateAction) RunPost(params struct {
DnsDomain: dnsDomain,
Domains: realDomains,
AutoRenew: params.AutoRenew,
AuthURL: params.AuthURL,
})
if err != nil {
this.ErrorPage(err)
@@ -138,6 +140,7 @@ func (this *CreateAction) RunPost(params struct {
DnsDomain: dnsDomain,
Domains: realDomains,
AutoRenew: params.AutoRenew,
AuthURL: params.AuthURL,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -61,6 +61,7 @@ func (this *UpdateTaskPopupAction) RunGet(params struct {
"domains": task.Domains,
"autoRenew": task.AutoRenew,
"isOn": task.IsOn,
"authURL": task.AuthURL,
"dnsProvider": dnsProviderMap,
}
@@ -94,6 +95,7 @@ func (this *UpdateTaskPopupAction) RunPost(params struct {
DnsDomain string
Domains []string
AutoRenew bool
AuthURL string
Must *actions.Must
CSRF *actionutils.CSRF
@@ -146,6 +148,7 @@ func (this *UpdateTaskPopupAction) RunPost(params struct {
DnsDomain: dnsDomain,
Domains: realDomains,
AutoRenew: params.AutoRenew,
AuthURL: params.AuthURL,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -22,6 +22,7 @@ func (this *ListsAction) RunGet(params struct {
Type string
}) {
this.Data["subMenuItem"] = params.Type
this.Data["type"] = params.Type
listId, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledPolicyIPListIdWithType(this.AdminContext(), params.FirewallPolicyId, params.Type)
if err != nil {

View File

@@ -0,0 +1,141 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
)
type BindHTTPFirewallPopupAction struct {
actionutils.ParentAction
}
func (this *BindHTTPFirewallPopupAction) Init() {
this.Nav("", "", "")
}
func (this *BindHTTPFirewallPopupAction) RunGet(params struct {
HttpFirewallPolicyId int64
Type string
}) {
this.Data["httpFirewallPolicyId"] = params.HttpFirewallPolicyId
// 获取已经选中的名单IDs
var selectedIds = []int64{}
inboundConfig, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledHTTPFirewallPolicyInboundConfig(this.AdminContext(), params.HttpFirewallPolicyId)
if err != nil {
this.ErrorPage(err)
return
}
if inboundConfig != nil {
for _, ref := range inboundConfig.PublicAllowListRefs {
selectedIds = append(selectedIds, ref.ListId)
}
for _, ref := range inboundConfig.PublicDenyListRefs {
selectedIds = append(selectedIds, ref.ListId)
}
}
// 公共的名单
countResp, err := this.RPC().IPListRPC().CountAllEnabledIPLists(this.AdminContext(), &pb.CountAllEnabledIPListsRequest{
Type: params.Type,
IsPublic: true,
Keyword: "",
})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
listsResp, err := this.RPC().IPListRPC().ListEnabledIPLists(this.AdminContext(), &pb.ListEnabledIPListsRequest{
Type: params.Type,
IsPublic: true,
Keyword: "",
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var listMaps = []maps.Map{}
for _, list := range listsResp.IpLists {
// 包含的IP数量
countItemsResp, err := this.RPC().IPItemRPC().CountIPItemsWithListId(this.AdminContext(), &pb.CountIPItemsWithListIdRequest{IpListId: list.Id})
if err != nil {
this.ErrorPage(err)
return
}
var countItems = countItemsResp.Count
listMaps = append(listMaps, maps.Map{
"id": list.Id,
"isOn": list.IsOn,
"name": list.Name,
"description": list.Description,
"countItems": countItems,
"type": list.Type,
"isSelected": lists.ContainsInt64(selectedIds, list.Id),
})
}
this.Data["lists"] = listMaps
this.Show()
}
func (this *BindHTTPFirewallPopupAction) RunPost(params struct {
HttpFirewallPolicyId int64
ListId int64
Must *actions.Must
}) {
defer this.CreateLogInfo("绑定IP名单 %d 到WAF策略 %d", params.ListId, params.HttpFirewallPolicyId)
// List类型
listResp, err := this.RPC().IPListRPC().FindEnabledIPList(this.AdminContext(), &pb.FindEnabledIPListRequest{IpListId: params.ListId})
if err != nil {
this.ErrorPage(err)
return
}
var list = listResp.IpList
if list == nil {
this.Fail("找不到要使用的IP名单")
}
// 已经绑定的
inboundConfig, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledHTTPFirewallPolicyInboundConfig(this.AdminContext(), params.HttpFirewallPolicyId)
if err != nil {
this.ErrorPage(err)
return
}
if inboundConfig == nil {
inboundConfig = &firewallconfigs.HTTPFirewallInboundConfig{IsOn: true}
}
inboundConfig.AddPublicList(list.Id, list.Type)
inboundJSON, err := json.Marshal(inboundConfig)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().HTTPFirewallPolicyRPC().UpdateHTTPFirewallInboundConfig(this.AdminContext(), &pb.UpdateHTTPFirewallInboundConfigRequest{
HttpFirewallPolicyId: params.HttpFirewallPolicyId,
InboundJSON: inboundJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,103 @@
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type CreateIPPopupAction struct {
actionutils.ParentAction
}
func (this *CreateIPPopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreateIPPopupAction) RunGet(params struct {
ListId int64
}) {
this.Data["listId"] = params.ListId
this.Show()
}
func (this *CreateIPPopupAction) RunPost(params struct {
ListId int64
IpFrom string
IpTo string
ExpiredAt int64
Reason string
Type string
EventLevel string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
// 校验IPList
existsResp, err := this.RPC().IPListRPC().ExistsEnabledIPList(this.AdminContext(), &pb.ExistsEnabledIPListRequest{IpListId: params.ListId})
if err != nil {
this.ErrorPage(err)
return
}
if !existsResp.Exists {
this.Fail("IP名单不存在")
}
switch params.Type {
case "ipv4":
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入开始IP")
// 校验IP格式ipFrom/ipTo
var ipFromLong uint64
if !utils.IsIPv4(params.IpFrom) {
this.Fail("请输入正确的开始IP")
}
ipFromLong = utils.IP2Long(params.IpFrom)
var ipToLong uint64
if len(params.IpTo) > 0 && !utils.IsIPv4(params.IpTo) {
ipToLong = utils.IP2Long(params.IpTo)
this.Fail("请输入正确的结束IP")
}
if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong {
params.IpTo, params.IpFrom = params.IpFrom, params.IpTo
}
case "ipv6":
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入IP")
// 校验IP格式ipFrom
if !utils.IsIPv6(params.IpFrom) {
this.Fail("请输入正确的IPv6地址")
}
case "all":
params.IpFrom = "0.0.0.0"
}
createResp, err := this.RPC().IPItemRPC().CreateIPItem(this.AdminContext(), &pb.CreateIPItemRequest{
IpListId: params.ListId,
IpFrom: params.IpFrom,
IpTo: params.IpTo,
ExpiredAt: params.ExpiredAt,
Reason: params.Reason,
Type: params.Type,
EventLevel: params.EventLevel,
})
if err != nil {
this.ErrorPage(err)
return
}
itemId := createResp.IpItemId
// 日志
defer this.CreateLog(oplogs.LevelInfo, "在IP名单中添加IP %d", itemId)
this.Success()
}

View File

@@ -0,0 +1,64 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct {
Type string
}) {
this.Data["type"] = params.Type
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
Type string
Description string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
var listId int64 = 0
defer func() {
defer this.CreateLogInfo("创建IP名单 %d", listId)
}()
params.Must.
Field("name", params.Name).
Require("请输入名称")
createResp, err := this.RPC().IPListRPC().CreateIPList(this.AdminContext(), &pb.CreateIPListRequest{
Type: params.Type,
Name: params.Name,
Code: "",
TimeoutJSON: nil,
IsPublic: true,
Description: params.Description,
})
if err != nil {
this.ErrorPage(err)
return
}
listId = createResp.IpListId
this.Data["list"] = maps.Map{
"type": params.Type,
}
this.Success()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
ListId int64
}) {
defer this.CreateLogInfo("删除IP名单 %d", params.ListId)
// 删除
_, err := this.RPC().IPListRPC().DeleteIPList(this.AdminContext(), &pb.DeleteIPListRequest{IpListId: params.ListId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,26 @@
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteIPAction struct {
actionutils.ParentAction
}
func (this *DeleteIPAction) RunPost(params struct {
ItemId int64
}) {
// 日志
defer this.CreateLog(oplogs.LevelInfo, "从IP名单中删除IP %d", params.ItemId)
_, err := this.RPC().IPItemRPC().DeleteIPItem(this.AdminContext(), &pb.DeleteIPItemRequest{IpItemId: params.ItemId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,25 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type ExportAction struct {
actionutils.ParentAction
}
func (this *ExportAction) Init() {
this.Nav("", "", "export")
}
func (this *ExportAction) RunGet(params struct {
ListId int64
}) {
err := InitIPList(this.Parent(), params.ListId)
if err != nil {
this.ErrorPage(err)
return
}
this.Show()
}

View File

@@ -0,0 +1,57 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/golang/protobuf/proto"
"strconv"
)
type ExportDataAction struct {
actionutils.ParentAction
}
func (this *ExportDataAction) Init() {
this.Nav("", "", "")
}
func (this *ExportDataAction) RunGet(params struct {
ListId int64
}) {
defer this.CreateLogInfo("导出IP名单 %d", params.ListId)
resp := &pb.ListIPItemsWithListIdResponse{}
var offset int64 = 0
var size int64 = 1000
for {
itemsResp, err := this.RPC().IPItemRPC().ListIPItemsWithListId(this.AdminContext(), &pb.ListIPItemsWithListIdRequest{
IpListId: params.ListId,
Offset: offset,
Size: size,
})
if err != nil {
this.ErrorPage(err)
return
}
if len(itemsResp.IpItems) == 0 {
break
}
for _, item := range itemsResp.IpItems {
resp.IpItems = append(resp.IpItems, item)
}
offset += size
}
data, err := proto.Marshal(resp)
if err != nil {
this.ErrorPage(err)
return
}
this.AddHeader("Content-Disposition", "attachment; filename=\"ip-list-"+numberutils.FormatInt64(params.ListId)+".data\";")
this.AddHeader("Content-Length", strconv.Itoa(len(data)))
this.Write(data)
}

View File

@@ -0,0 +1,59 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ipconfigs"
"github.com/iwind/TeaGo/maps"
)
// HttpFirewallAction 显示已经绑定的IP名单
type HttpFirewallAction struct {
actionutils.ParentAction
}
func (this *HttpFirewallAction) RunPost(params struct {
HttpFirewallPolicyId int64
Type string
}) {
inboundConfig, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledHTTPFirewallPolicyInboundConfig(this.AdminContext(), params.HttpFirewallPolicyId)
if err != nil {
this.ErrorPage(err)
return
}
if inboundConfig == nil {
inboundConfig = &firewallconfigs.HTTPFirewallInboundConfig{IsOn: true}
}
var refs []*ipconfigs.IPListRef
switch params.Type {
case ipconfigs.IPListTypeBlack:
refs = inboundConfig.PublicDenyListRefs
case ipconfigs.IPListTypeWhite:
refs = inboundConfig.PublicAllowListRefs
}
listMaps := []maps.Map{}
for _, ref := range refs {
listResp, err := this.RPC().IPListRPC().FindEnabledIPList(this.AdminContext(), &pb.FindEnabledIPListRequest{IpListId: ref.ListId})
if err != nil {
this.ErrorPage(err)
return
}
var list = listResp.IpList
if list == nil {
continue
}
listMaps = append(listMaps, maps.Map{
"id": list.Id,
"name": list.Name,
})
}
this.Data["lists"] = listMaps
this.Success()
}

View File

@@ -0,0 +1,87 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/golang/protobuf/proto"
"github.com/iwind/TeaGo/actions"
)
type ImportAction struct {
actionutils.ParentAction
}
func (this *ImportAction) Init() {
this.Nav("", "", "import")
}
func (this *ImportAction) RunGet(params struct {
ListId int64
}) {
err := InitIPList(this.Parent(), params.ListId)
if err != nil {
this.ErrorPage(err)
return
}
this.Show()
}
func (this *ImportAction) RunPost(params struct {
ListId int64
File *actions.File
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("导入IP名单 %d", params.ListId)
existsResp, err := this.RPC().IPListRPC().ExistsEnabledIPList(this.AdminContext(), &pb.ExistsEnabledIPListRequest{IpListId: params.ListId})
if err != nil {
this.ErrorPage(err)
return
}
if !existsResp.Exists {
this.Fail("IP名单不存在")
}
if params.File == nil {
this.Fail("请选择要导入的IP文件")
}
data, err := params.File.Read()
if err != nil {
this.ErrorPage(err)
return
}
resp := &pb.ListIPItemsWithListIdResponse{}
err = proto.Unmarshal(data, resp)
if err != nil {
this.Fail("导入失败,文件格式错误:" + err.Error())
}
var count = 0
var countIgnore = 0
for _, item := range resp.IpItems {
_, err = this.RPC().IPItemRPC().CreateIPItem(this.AdminContext(), &pb.CreateIPItemRequest{
IpListId: params.ListId,
IpFrom: item.IpFrom,
IpTo: item.IpTo,
ExpiredAt: item.ExpiredAt,
Reason: item.Reason,
Type: item.Type,
EventLevel: item.EventLevel,
})
if err != nil {
this.Fail("导入过程中出错:" + err.Error())
}
count++
}
this.Data["count"] = count
this.Data["countIgnore"] = countIgnore
this.Success()
}

View File

@@ -0,0 +1,76 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ipconfigs"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "index")
}
func (this *IndexAction) RunGet(params struct {
Type string
Keyword string
}) {
if len(params.Type) == 0 {
params.Type = ipconfigs.IPListTypeBlack
}
this.Data["type"] = params.Type
this.Data["keyword"] = params.Keyword
countResp, err := this.RPC().IPListRPC().CountAllEnabledIPLists(this.AdminContext(), &pb.CountAllEnabledIPListsRequest{
Type: params.Type,
IsPublic: true,
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
listsResp, err := this.RPC().IPListRPC().ListEnabledIPLists(this.AdminContext(), &pb.ListEnabledIPListsRequest{
Type: params.Type,
IsPublic: true,
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var listMaps = []maps.Map{}
for _, list := range listsResp.IpLists {
// 包含的IP数量
countItemsResp, err := this.RPC().IPItemRPC().CountIPItemsWithListId(this.AdminContext(), &pb.CountIPItemsWithListIdRequest{IpListId: list.Id})
if err != nil {
this.ErrorPage(err)
return
}
var countItems = countItemsResp.Count
listMaps = append(listMaps, maps.Map{
"id": list.Id,
"isOn": list.IsOn,
"name": list.Name,
"description": list.Description,
"countItems": countItems,
"type": list.Type,
})
}
this.Data["lists"] = listMaps
this.Show()
}

View File

@@ -0,0 +1,39 @@
package iplists
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.AdminModuleCodeServer)).
Data("teaMenu", "servers").
Data("teaSubMenu", "iplist").
Prefix("/servers/iplists").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
Get("/list", new(ListAction)).
GetPost("/import", new(ImportAction)).
GetPost("/export", new(ExportAction)).
Get("/exportData", new(ExportDataAction)).
Post("/delete", new(DeleteAction)).
GetPost("/test", new(TestAction)).
GetPost("/update", new(UpdateAction)).
Get("/items", new(ItemsAction)).
// IP相关
GetPost("/createIPPopup", new(CreateIPPopupAction)).
GetPost("/updateIPPopup", new(UpdateIPPopupAction)).
Post("/deleteIP", new(DeleteIPAction)).
// 防火墙
GetPost("/bindHTTPFirewallPopup", new(BindHTTPFirewallPopupAction)).
Post("/unbindHTTPFirewall", new(UnbindHTTPFirewallAction)).
Post("/httpFirewall", new(HttpFirewallAction)).
EndAll()
})
}

View File

@@ -0,0 +1,71 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type ItemsAction struct {
actionutils.ParentAction
}
func (this *ItemsAction) Init() {
this.Nav("", "", "item")
}
func (this *ItemsAction) RunGet(params struct {
ListId int64
}) {
err := InitIPList(this.Parent(), params.ListId)
if err != nil {
this.ErrorPage(err)
return
}
// 数量
var listId = params.ListId
countResp, err := this.RPC().IPItemRPC().CountIPItemsWithListId(this.AdminContext(), &pb.CountIPItemsWithListIdRequest{IpListId: listId})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
// 列表
itemsResp, err := this.RPC().IPItemRPC().ListIPItemsWithListId(this.AdminContext(), &pb.ListIPItemsWithListIdRequest{
IpListId: listId,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
itemMaps := []maps.Map{}
for _, item := range itemsResp.IpItems {
expiredTime := ""
if item.ExpiredAt > 0 {
expiredTime = timeutil.FormatTime("Y-m-d H:i:s", item.ExpiredAt)
}
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"ipFrom": item.IpFrom,
"ipTo": item.IpTo,
"expiredTime": expiredTime,
"reason": item.Reason,
"type": item.Type,
"eventLevelName": firewallconfigs.FindFirewallEventLevelName(item.EventLevel),
})
}
this.Data["items"] = itemMaps
this.Show()
}

View File

@@ -0,0 +1,25 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type ListAction struct {
actionutils.ParentAction
}
func (this *ListAction) Init() {
this.Nav("", "", "list")
}
func (this *ListAction) RunGet(params struct{
ListId int64
}) {
err := InitIPList(this.Parent(), params.ListId)
if err != nil {
this.ErrorPage(err)
return
}
this.Show()
}

View File

@@ -0,0 +1,74 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type TestAction struct {
actionutils.ParentAction
}
func (this *TestAction) Init() {
this.Nav("", "", "test")
}
func (this *TestAction) RunGet(params struct {
ListId int64
}) {
err := InitIPList(this.Parent(), params.ListId)
if err != nil {
this.ErrorPage(err)
return
}
this.Show()
}
func (this *TestAction) RunPost(params struct {
ListId int64
Ip string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
resp, err := this.RPC().IPItemRPC().CheckIPItemStatus(this.AdminContext(), &pb.CheckIPItemStatusRequest{
IpListId: params.ListId,
Ip: params.Ip,
})
if err != nil {
this.ErrorPage(err)
return
}
resultMap := maps.Map{
"isDone": true,
"isFound": resp.IsFound,
"isOk": resp.IsOk,
"error": resp.Error,
"isAllowed": resp.IsAllowed,
}
if resp.IpItem != nil {
resultMap["item"] = maps.Map{
"id": resp.IpItem.Id,
"ipFrom": resp.IpItem.IpFrom,
"ipTo": resp.IpItem.IpTo,
"reason": resp.IpItem.Reason,
"expiredAt": resp.IpItem.ExpiredAt,
"expiredTime": timeutil.FormatTime("Y-m-d H:i:s", resp.IpItem.ExpiredAt),
"type": resp.IpItem.Type,
"eventLevelName": firewallconfigs.FindFirewallEventLevelName(resp.IpItem.EventLevel),
}
}
this.Data["result"] = resultMap
this.Success()
}

View File

@@ -0,0 +1,60 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
)
type UnbindHTTPFirewallAction struct {
actionutils.ParentAction
}
func (this *UnbindHTTPFirewallAction) RunPost(params struct {
HttpFirewallPolicyId int64
ListId int64
}) {
defer this.CreateLogInfo("接触绑定IP名单 %d WAF策略 %d", params.ListId, params.HttpFirewallPolicyId)
// List类型
listResp, err := this.RPC().IPListRPC().FindEnabledIPList(this.AdminContext(), &pb.FindEnabledIPListRequest{IpListId: params.ListId})
if err != nil {
this.ErrorPage(err)
return
}
var list = listResp.IpList
if list == nil {
this.Fail("找不到要使用的IP名单")
}
// 已经绑定的
inboundConfig, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledHTTPFirewallPolicyInboundConfig(this.AdminContext(), params.HttpFirewallPolicyId)
if err != nil {
this.ErrorPage(err)
return
}
if inboundConfig == nil {
inboundConfig = &firewallconfigs.HTTPFirewallInboundConfig{IsOn: true}
}
inboundConfig.RemovePublicList(list.Id, list.Type)
inboundJSON, err := json.Marshal(inboundConfig)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().HTTPFirewallPolicyRPC().UpdateHTTPFirewallInboundConfig(this.AdminContext(), &pb.UpdateHTTPFirewallInboundConfigRequest{
HttpFirewallPolicyId: params.HttpFirewallPolicyId,
InboundJSON: inboundJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,58 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type UpdateAction struct {
actionutils.ParentAction
}
func (this *UpdateAction) Init() {
this.Nav("", "", "update")
}
func (this *UpdateAction) RunGet(params struct {
ListId int64
}) {
err := InitIPList(this.Parent(), params.ListId)
if err != nil {
this.ErrorPage(err)
return
}
this.Show()
}
func (this *UpdateAction) RunPost(params struct {
ListId int64
Name string
Type string
Description string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("修改IP名单 %d", params.ListId)
params.Must.
Field("name", params.Name).
Require("请输入名称")
_, err := this.RPC().IPListRPC().UpdateIPList(this.AdminContext(), &pb.UpdateIPListRequest{
IpListId: params.ListId,
Name: params.Name,
Code: "",
TimeoutJSON: nil,
Description: params.Description,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,117 @@
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdateIPPopupAction struct {
actionutils.ParentAction
}
func (this *UpdateIPPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdateIPPopupAction) RunGet(params struct {
ItemId int64
}) {
itemResp, err := this.RPC().IPItemRPC().FindEnabledIPItem(this.AdminContext(), &pb.FindEnabledIPItemRequest{IpItemId: params.ItemId})
if err != nil {
this.ErrorPage(err)
return
}
item := itemResp.IpItem
if item == nil {
this.NotFound("ipItem", params.ItemId)
return
}
this.Data["item"] = maps.Map{
"id": item.Id,
"ipFrom": item.IpFrom,
"ipTo": item.IpTo,
"expiredAt": item.ExpiredAt,
"reason": item.Reason,
"type": item.Type,
"eventLevel": item.EventLevel,
}
this.Data["type"] = item.Type
this.Show()
}
func (this *UpdateIPPopupAction) RunPost(params struct {
ItemId int64
IpFrom string
IpTo string
ExpiredAt int64
Reason string
Type string
EventLevel string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
// 日志
defer this.CreateLog(oplogs.LevelInfo, "修改IP名单中IP %d", params.ItemId)
// TODO 校验ItemId所属用户
switch params.Type {
case "ipv4":
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入开始IP")
// 校验IP格式ipFrom/ipTo
var ipFromLong uint64
if !utils.IsIPv4(params.IpFrom) {
this.Fail("请输入正确的开始IP")
}
ipFromLong = utils.IP2Long(params.IpFrom)
var ipToLong uint64
if len(params.IpTo) > 0 && !utils.IsIPv4(params.IpTo) {
ipToLong = utils.IP2Long(params.IpTo)
this.Fail("请输入正确的结束IP")
}
if ipFromLong > 0 && ipToLong > 0 && ipFromLong > ipToLong {
params.IpTo, params.IpFrom = params.IpFrom, params.IpTo
}
case "ipv6":
params.Must.
Field("ipFrom", params.IpFrom).
Require("请输入IP")
// 校验IP格式ipFrom
if !utils.IsIPv6(params.IpFrom) {
this.Fail("请输入正确的IPv6地址")
}
case "all":
params.IpFrom = "0.0.0.0"
}
_, err := this.RPC().IPItemRPC().UpdateIPItem(this.AdminContext(), &pb.UpdateIPItemRequest{
IpItemId: params.ItemId,
IpFrom: params.IpFrom,
IpTo: params.IpTo,
ExpiredAt: params.ExpiredAt,
Reason: params.Reason,
Type: params.Type,
EventLevel: params.EventLevel,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,52 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"errors"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
func InitIPList(action *actionutils.ParentAction, listId int64) error {
client, err := rpc.SharedRPC()
if err != nil {
return err
}
listResp, err := client.IPListRPC().FindEnabledIPList(action.AdminContext(), &pb.FindEnabledIPListRequest{IpListId: listId})
if err != nil {
return err
}
list := listResp.IpList
if list == nil {
return errors.New("not found")
}
var typeName = ""
switch list.Type {
case "black":
typeName = "黑名单"
case "white":
typeName = "白名单"
}
// IP数量
countItemsResp, err := client.IPItemRPC().CountIPItemsWithListId(action.AdminContext(), &pb.CountIPItemsWithListIdRequest{IpListId: listId})
if err != nil {
return err
}
countItems := countItemsResp.Count
action.Data["list"] = maps.Map{
"id": list.Id,
"name": list.Name,
"type": list.Type,
"typeName": typeName,
"description": list.Description,
"isOn": list.IsOn,
"countItems": countItems,
}
return nil
}

View File

@@ -0,0 +1,25 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type ChartsAction struct {
actionutils.ParentAction
}
func (this *ChartsAction) Init() {
this.Nav("", "", "chart")
}
func (this *ChartsAction) RunGet(params struct {
ItemId int64
}) {
err := InitItem(this.Parent(), params.ItemId)
if err != nil {
this.ErrorPage(err)
return
}
this.Show()
}

View File

@@ -0,0 +1,83 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct {
Category string
}) {
this.Data["category"] = params.Category
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
Category string
KeysJSON []byte
PeriodJSON []byte
Value string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入指标名称")
if len(params.Category) == 0 {
this.Fail("请选择指标类型")
}
// 统计对象
if len(params.KeysJSON) == 0 {
this.FailField("keys", "请选择指标统计的对象")
}
var keys = []string{}
err := json.Unmarshal(params.KeysJSON, &keys)
if err != nil {
this.FailField("keys", "解析指标对象失败")
}
if len(keys) == 0 {
this.FailField("keys", "请选择指标统计的对象")
}
var periodMap = maps.Map{}
err = json.Unmarshal(params.PeriodJSON, &periodMap)
if err != nil {
this.FailField("period", "解析统计周期失败")
}
var period = periodMap.GetInt32("period")
var periodUnit = periodMap.GetString("unit")
createResp, err := this.RPC().MetricItemRPC().CreateMetricItem(this.AdminContext(), &pb.CreateMetricItemRequest{
Code: "", // TODO 未来实现
Category: params.Category,
Name: params.Name,
Keys: keys,
Period: period,
PeriodUnit: periodUnit,
Value: params.Value,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo("创建统计指标 %d", createResp.MetricItemId)
this.Success()
}

View File

@@ -0,0 +1,26 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
ItemId int64
}) {
defer this.CreateLogInfo("删除统计指标")
_, err := this.RPC().MetricItemRPC().DeleteMetricItem(this.AdminContext(), &pb.DeleteMetricItemRequest{MetricItemId: params.ItemId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,61 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct {
Category string
}) {
if len(params.Category) == 0 {
params.Category = "http"
}
this.Data["category"] = params.Category
countResp, err := this.RPC().MetricItemRPC().CountAllEnabledMetricItems(this.AdminContext(), &pb.CountAllEnabledMetricItemsRequest{Category: params.Category})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
itemsResp, err := this.RPC().MetricItemRPC().ListEnabledMetricItems(this.AdminContext(), &pb.ListEnabledMetricItemsRequest{
Category: params.Category,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var itemMaps = []maps.Map{}
for _, item := range itemsResp.MetricItems {
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"name": item.Name,
"isOn": item.IsOn,
"period": item.Period,
"periodUnit": item.PeriodUnit,
"keys": item.Keys,
"value": item.Value,
"category": item.Category,
})
}
this.Data["items"] = itemMaps
this.Show()
}

View File

@@ -0,0 +1,24 @@
package metrics
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.AdminModuleCodeServer)).
Data("teaMenu", "servers").
Data("teaSubMenu", "metric").
Prefix("/servers/metrics").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/update", new(UpdateAction)).
Post("/delete", new(DeleteAction)).
Get("/item", new(ItemAction)).
Get("/charts", new(ChartsAction)).
EndAll()
})
}

View File

@@ -0,0 +1,25 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type ItemAction struct {
actionutils.ParentAction
}
func (this *ItemAction) Init() {
this.Nav("", "", "item")
}
func (this *ItemAction) RunGet(params struct {
ItemId int64
}) {
err := InitItem(this.Parent(), params.ItemId)
if err != nil {
this.ErrorPage(err)
return
}
this.Show()
}

View File

@@ -0,0 +1,84 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdateAction struct {
actionutils.ParentAction
}
func (this *UpdateAction) Init() {
this.Nav("", "", "update")
}
func (this *UpdateAction) RunGet(params struct {
ItemId int64
}) {
err := InitItem(this.Parent(), params.ItemId)
if err != nil {
this.ErrorPage(err)
return
}
this.Show()
}
func (this *UpdateAction) RunPost(params struct {
ItemId int64
Name string
KeysJSON []byte
PeriodJSON []byte
Value string
IsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入指标名称")
// 统计对象
if len(params.KeysJSON) == 0 {
this.FailField("keys", "请选择指标统计的对象")
}
var keys = []string{}
err := json.Unmarshal(params.KeysJSON, &keys)
if err != nil {
this.FailField("keys", "解析指标对象失败")
}
if len(keys) == 0 {
this.FailField("keys", "请选择指标统计的对象")
}
var periodMap = maps.Map{}
err = json.Unmarshal(params.PeriodJSON, &periodMap)
if err != nil {
this.FailField("period", "解析统计周期失败")
}
var period = periodMap.GetInt32("period")
var periodUnit = periodMap.GetString("unit")
_, err = this.RPC().MetricItemRPC().UpdateMetricItem(this.AdminContext(), &pb.UpdateMetricItemRequest{
MetricItemId: params.ItemId,
Name: params.Name,
Keys: keys,
Period: period,
PeriodUnit: periodUnit,
Value: params.Value,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo("修改统计指标 %d", params.ItemId)
this.Success()
}

View File

@@ -0,0 +1,37 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import (
"errors"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
func InitItem(parent *actionutils.ParentAction, itemId int64) error {
client, err := rpc.SharedRPC()
if err != nil {
return err
}
resp, err := client.MetricItemRPC().FindEnabledMetricItem(parent.AdminContext(), &pb.FindEnabledMetricItemRequest{MetricItemId: itemId})
if err != nil {
return err
}
var item = resp.MetricItem
if item == nil {
return errors.New("not found")
}
parent.Data["item"] = maps.Map{
"id": item.Id,
"name": item.Name,
"isOn": item.IsOn,
"keys": item.Keys,
"value": item.Value,
"period": item.Period,
"periodUnit": item.PeriodUnit,
"category": item.Category,
}
return nil
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/go-yaml/yaml"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/actions"
@@ -200,6 +201,26 @@ func (this *InstallAction) RunPost(params struct {
this.Fail("设置管理员账号出错:" + err.Error())
}
// 设置访问日志保留天数
var accessLogKeepDays = dbMap.GetInt("accessLogKeepDays")
if accessLogKeepDays > 0 {
var config = &systemconfigs.DatabaseConfig{}
config.ServerAccessLog.Clean.Days = accessLogKeepDays
configJSON, err := json.Marshal(config)
if err != nil {
this.Fail("配置设置访问日志保留天数出错:" + err.Error())
return
}
_, err = client.SysSettingRPC().UpdateSysSetting(ctx, &pb.UpdateSysSettingRequest{
Code: systemconfigs.SettingCodeDatabaseConfigSetting,
ValueJSON: configJSON,
})
if err != nil {
this.Fail("配置设置访问日志保留天数出错:" + err.Error())
return
}
}
err = apiConfig.WriteFile(Tea.ConfigFile("api.yaml"))
if err != nil {
this.Fail("保存配置失败,原因:" + err.Error())
@@ -232,6 +253,26 @@ func (this *InstallAction) RunPost(params struct {
this.Fail("设置管理员账号出错:" + err.Error())
}
// 设置访问日志保留天数
var accessLogKeepDays = dbMap.GetInt("accessLogKeepDays")
if accessLogKeepDays > 0 {
var config = &systemconfigs.DatabaseConfig{}
config.ServerAccessLog.Clean.Days = accessLogKeepDays
configJSON, err := json.Marshal(config)
if err != nil {
this.Fail("配置设置访问日志保留天数出错:" + err.Error())
return
}
_, err = client.SysSettingRPC().UpdateSysSetting(ctx, &pb.UpdateSysSettingRequest{
Code: systemconfigs.SettingCodeDatabaseConfigSetting,
ValueJSON: configJSON,
})
if err != nil {
this.Fail("配置设置访问日志保留天数出错:" + err.Error())
return
}
}
// 写入API节点配置完成安装
err = apiConfig.WriteFile(Tea.ConfigFile("api.yaml"))
if err != nil {

View File

@@ -15,11 +15,12 @@ type ValidateDbAction struct {
}
func (this *ValidateDbAction) RunPost(params struct {
Host string
Port string
Database string
Username string
Password string
Host string
Port string
Database string
Username string
Password string
AccessLogKeepDays int
Must *actions.Must
}) {
@@ -98,12 +99,13 @@ func (this *ValidateDbAction) RunPost(params struct {
}
this.Data["db"] = maps.Map{
"host": params.Host,
"port": params.Port,
"database": params.Database,
"username": params.Username,
"password": params.Password,
"passwordMask": strings.Repeat("*", len(params.Password)),
"host": params.Host,
"port": params.Port,
"database": params.Database,
"username": params.Username,
"password": params.Password,
"passwordMask": strings.Repeat("*", len(params.Password)),
"accessLogKeepDays": params.AccessLogKeepDays,
}
this.Success()

View File

@@ -2,22 +2,33 @@ package ui
import (
"bytes"
"crypto/md5"
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/conds/condutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/files"
"github.com/iwind/TeaGo/logs"
"net/http"
)
type ComponentsAction actions.Action
var componentsData = []byte{}
var componentsDataSum string
func (this *ComponentsAction) RunGet(params struct{}) {
this.AddHeader("Content-Type", "text/javascript; charset=utf-8")
// etag
var requestETag = this.Header("If-None-Match")
if len(requestETag) > 0 && requestETag == "\""+componentsDataSum+"\"" {
this.ResponseWriter.WriteHeader(http.StatusNotModified)
return
}
if !Tea.IsTesting() && len(componentsData) > 0 {
this.AddHeader("Last-Modified", "Fri, 06 Sep 2019 08:29:50 GMT")
this.Write(componentsData)
@@ -81,5 +92,12 @@ func (this *ComponentsAction) RunGet(params struct{}) {
}
componentsData = buffer.Bytes()
// ETag
var h = md5.New()
h.Write(buffer.Bytes())
componentsDataSum = fmt.Sprintf("%x", h.Sum(nil))
this.AddHeader("ETag", "\""+componentsDataSum+"\"")
this.Write(componentsData)
}

View File

@@ -0,0 +1,30 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package ui
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/Tea"
"io/ioutil"
)
type HideTipAction struct {
actionutils.ParentAction
}
func (this *HideTipAction) RunPost(params struct {
Code string
}) {
tipKeyLocker.Lock()
tipKeyMap[params.Code] = true
tipKeyLocker.Unlock()
// 保存到文件
tipJSON, err := json.Marshal(tipKeyMap)
if err == nil {
_ = ioutil.WriteFile(Tea.ConfigFile(tipConfigFile), tipJSON, 0666)
}
this.Success()
}

View File

@@ -27,6 +27,8 @@ func init() {
GetPost("/selectProvincesPopup", new(SelectProvincesPopupAction)).
GetPost("/selectCountriesPopup", new(SelectCountriesPopupAction)).
Post("/eventLevelOptions", new(EventLevelOptionsAction)).
Post("/showTip", new(ShowTipAction)).
Post("/hideTip", new(HideTipAction)).
EndAll()
})

View File

@@ -0,0 +1,48 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package ui
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo"
"github.com/iwind/TeaGo/Tea"
"io/ioutil"
"sync"
)
var tipKeyMap = map[string]bool{}
var tipKeyLocker = sync.Mutex{}
var tipConfigFile = "tip.json"
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
// 从配置文件中加载已关闭的tips
data, err := ioutil.ReadFile(Tea.ConfigFile(tipConfigFile))
if err == nil {
var m = map[string]bool{}
err = json.Unmarshal(data, &m)
if err == nil {
tipKeyLocker.Lock()
tipKeyMap = m
tipKeyLocker.Unlock()
}
}
})
}
type ShowTipAction struct {
actionutils.ParentAction
}
func (this *ShowTipAction) RunPost(params struct {
Code string
}) {
tipKeyLocker.Lock()
_, ok := tipKeyMap[params.Code]
tipKeyLocker.Unlock()
this.Data["visible"] = !ok
this.Success()
}

View File

@@ -167,11 +167,21 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
"url": "/servers/components/waf",
"code": "waf",
},
{
"name": "IP名单",
"url": "/servers/iplists",
"code": "iplist",
},
{
"name": "证书管理",
"url": "/servers/certs",
"code": "cert",
},
/**{
"name": "统计指标",
"url": "/servers/metrics",
"code": "metric",
},**/
},
},
{

View File

@@ -109,6 +109,9 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/websocket"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/stat"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/iplists"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/metrics"
// 设置相关
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/authority"

View File

@@ -176,6 +176,7 @@ Vue.component("health-check-config-box", {
<td>域名</td>
<td>
<input type="text" v-model="urlHost"/>
<p class="comment">在此集群上可以访问到的一个域名。</p>
</td>
</tr>
<tr>

View File

@@ -62,7 +62,7 @@ Vue.component("tip-icon", {
teaweb.popupTip(this.content)
}
},
template: `<a href="" title="查看帮助" @click.prevent="showTip"><i class="icon question circle"></i></a>`
template: `<a href="" title="查看帮助" @click.prevent="showTip"><i class="icon question circle grey"></i></a>`
})
// 提交点击事件

View File

@@ -0,0 +1,37 @@
// 信息提示窗口
Vue.component("tip-message-box", {
props: ["code"],
mounted: function () {
let that = this
Tea.action("/ui/showTip")
.params({
code: this.code
})
.success(function (resp) {
that.visible = resp.data.visible
})
.post()
},
data: function () {
return {
visible: false
}
},
methods: {
close: function () {
this.visible = false
Tea.action("/ui/hideTip")
.params({
code: this.code
})
.post()
}
},
template: `<div class="ui icon message" v-if="visible">
<i class="icon info circle"></i>
<i class="close icon" title="取消" @click.prevent="close"></i>
<div class="content">
<slot></slot>
</div>
</div>`
})

View File

@@ -0,0 +1,62 @@
// 绑定IP列表
Vue.component("ip-list-bind-box", {
props: ["v-http-firewall-policy-id", "v-type"],
mounted: function () {
this.refresh()
},
data: function () {
return {
policyId: this.vHttpFirewallPolicyId,
type: this.vType,
lists: []
}
},
methods: {
bind: function () {
let that = this
teaweb.popup("/servers/iplists/bindHTTPFirewallPopup?httpFirewallPolicyId=" + this.policyId + "&type=" + this.type, {
width: "50em",
height: "34em",
callback: function () {
},
onClose: function () {
that.refresh()
}
})
},
remove: function (index, listId) {
let that = this
teaweb.confirm("确定要删除这个绑定的IP名单吗", function () {
Tea.action("/servers/iplists/unbindHTTPFirewall")
.params({
httpFirewallPolicyId: that.policyId,
listId: listId
})
.post()
.success(function (resp) {
that.lists.$remove(index)
})
})
},
refresh: function () {
let that = this
Tea.action("/servers/iplists/httpFirewall")
.params({
httpFirewallPolicyId: this.policyId,
type: this.vType
})
.post()
.success(function (resp) {
that.lists = resp.data.lists
})
}
},
template: `<div>
<a href="" @click.prevent="bind()">绑定+</a> &nbsp; <span v-if="lists.length > 0"><span class="disabled small">|&nbsp;</span> 已绑定:</span>
<div class="ui label basic small" v-for="(list, index) in lists">
{{list.name}}
<a href="" title="删除" @click.prevent="remove(index, list.id)"><i class="icon remove small"></i></a>
</div>
</div>`
})

View File

@@ -34,7 +34,7 @@ Vue.component("http-access-log-box", {
this.select()
teaweb.popup("/servers/server/log/viewPopup?requestId=" + requestId, {
width: "50em",
height: "24em",
height: "28em",
onClose: function () {
that.deselect()
}

View File

@@ -0,0 +1,59 @@
// 指标对象
Vue.component("metric-keys-config-box", {
props: ["v-keys"],
data: function () {
let keys = this.vKeys
if (keys == null) {
keys = []
}
return {
keys: keys,
isAdding: false,
key: ""
}
},
methods: {
cancel: function () {
this.key = ""
this.isAdding = false
},
confirm: function () {
if (this.key.length > 0) {
this.keys.push(this.key)
this.cancel()
}
},
add: function () {
this.isAdding = true
let that = this
setTimeout(function () {
that.$refs.key.focus()
}, 100)
},
remove: function (index) {
this.keys.$remove(index)
}
},
template: `<div>
<input type="hidden" name="keysJSON" :value="JSON.stringify(keys)"/>
<div>
<div v-for="(key, index) in keys" class="ui label small basic">
{{key}} &nbsp; <a href="" title="删除" @click.prevent="remove"><i class="icon remove small"></i></a>
</div>
</div>
<div v-if="isAdding" style="margin-top: 1em">
<div class="ui fields inline">
<div class="ui field">
<input type="text" v-model="key" ref="key" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
</div>
<div class="ui field">
<button type="button" class="ui button tiny" @click.prevent="confirm">确定</button>
<a href="" @click.prevent="cancel"><i class="icon remove small"></i></a>
</div>
</div>
</div>
<div style="margin-top: 1em" v-if="!isAdding">
<button type="button" class="ui button tiny" @click.prevent="add">+</button>
</div>
</div>`
})

View File

@@ -0,0 +1,47 @@
// 指标周期设置
Vue.component("metric-period-config-box", {
props: ["v-period", "v-period-unit"],
data: function () {
let period = this.vPeriod
let periodUnit = this.vPeriodUnit
if (period == null || period.toString().length == 0) {
period = 1
}
if (periodUnit == null || periodUnit.length == 0) {
periodUnit = "day"
}
return {
periodConfig: {
period: period,
unit: periodUnit
}
}
},
watch: {
"periodConfig.period": function (v) {
v = parseInt(v)
if (isNaN(v) || v <= 0) {
v = 1
}
this.periodConfig.period = v
}
},
template: `<div>
<input type="hidden" name="periodJSON" :value="JSON.stringify(periodConfig)"/>
<div class="ui fields inline">
<div class="ui field">
<input type="text" v-model="periodConfig.period" maxlength="4" size="4"/>
</div>
<div class="ui field">
<select class="ui dropdown" v-model="periodConfig.unit">
<option value="minute">分钟</option>
<option value="hour">小时</option>
<option value="day">天</option>
<option value="week">周</option>
<option value="month">月</option>
</select>
</div>
</div>
<p class="comment">在此周期内同一对象累积为同一数据。</p>
</div>`
})

View File

@@ -219,9 +219,9 @@ window.teaweb = {
},
popupTip: function (html) {
Swal.fire({
html: '<i class="icon question circle"></i><span style="line-height: 1.7">' + html + "</span>",
html: '<div style="line-height: 1.7;text-align: left "><i class="icon question circle"></i>' + html + "</div>",
width: "30em",
padding: "5em",
padding: "4em",
showConfirmButton: false,
showCloseButton: true,
focusConfirm: false

View File

@@ -197,9 +197,6 @@ p.margin {
padding-top: 2em;
padding-bottom: 2.4em;
}
.main-menu .ui.menu .item span {
display: none;
}
}
.main-menu .ui.labeled.icon.menu .item {
font-size: 0.9em;
@@ -214,9 +211,24 @@ p.margin {
margin-top: 0.5em;
color: grey;
}
@media screen and (max-width: 512px) {
.main-menu .ui.menu .item.expend .subtitle {
display: none;
}
}
.main-menu .ui.menu .sub-items .item {
padding-left: 2.8em !important;
}
.main-menu .ui.menu .sub-items .item .icon {
position: absolute;
left: 1.1em;
top: 0.93em;
}
@media screen and (max-width: 512px) {
.main-menu .ui.menu .sub-items .item {
padding-left: 1em !important;
}
}
.main-menu .ui.menu .sub-items .item.active {
background-color: #2185d0 !important;
}

File diff suppressed because one or more lines are too long

View File

@@ -80,12 +80,12 @@
<span v-if="module.code.length > 0">
<i class="window restore outline icon" v-if="module.icon == null"></i>
<i class="ui icon" v-if="module.icon != null" :class="module.icon"></i>
<span>{{module.name}}</span>
<span class="module-name">{{module.name}}</span>
</span>
<div class="subtitle" v-if="module.subtitle != null && module.subtitle.length > 0">{{module.subtitle}}</div>
</a>
<div v-if="teaMenu == module.code" class="sub-items">
<a class="item" v-for="subItem in module.subItems" v-if="subItem.isOn !== false" :href="subItem.url" :class="{active:subItem.code == teaSubMenu}">{{subItem.name}}</a>
<a class="item" v-for="subItem in module.subItems" v-if="subItem.isOn !== false" :href="subItem.url"><i class="icon angle right" v-if="subItem.code == teaSubMenu"></i> {{subItem.name}}</a>
</div>
</div>
</div>

View File

@@ -139,7 +139,7 @@ div.margin, p.margin {
}
.main-menu .ui.menu .item span {
display: none;
}
}
@@ -163,9 +163,27 @@ div.margin, p.margin {
color: grey;
}
@media screen and (max-width: 512px) {
.item.expend .subtitle {
display: none;
}
}
.sub-items {
.item {
padding-left: 2.8em !important;
.icon {
position: absolute;
left: 1.1em;
top: 0.93em;
}
}
@media screen and (max-width: 512px) {
.item {
padding-left: 1em !important;
}
}
.item.active {

View File

@@ -6,7 +6,7 @@
<form method="post" class="ui form" v-if="!isRequesting">
<p>成功节点:<span class="green">{{countSuccess}}</span> &nbsp; 失败节点:<span class="red">{{countFail}}</span></p>
<table class="ui table selectable celled">
<table class="ui table selectable celled" v-if="results.length > 0">
<thead>
<tr>
<th>节点</th>

View File

@@ -0,0 +1,38 @@
{$layout "layout_popup"}
<h3>添加指标</h3>
<p class="comment" v-if="items.length == 0">暂时还没有指标。</p>
<table class="ui table celled selectable" v-if="items.length > 0">
<thead>
<tr>
<th>指标名称</th>
<th>统计对象</th>
<th>统计周期</th>
<th class="three wide">统计数值</th>
<th class="two wide">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="item in items">
<td>{{item.name}}</td>
<td>
<span v-if="item.keys != null" v-for="key in item.keys" class="ui label basic small">{{key}}</span>
</td>
<td>
{{item.period}} {{item.periodUnit}}
</td>
<td>
<span class="ui label small basic">{{item.value}}</span>
</td>
<td>
<label-on :v-is-on="item.isOn"></label-on>
</td>
<td>
<a href="" v-if="!item.isChecked">添加</a>
<a href="" v-if="item.isChecked"><span class="grey">已添加</span></a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,47 @@
{$layout}
{$template "/left_menu"}
<div class="right-box">
<first-menu>
<menu-item :href="'/clusters/cluster/settings/metrics?category=http&clusterId=' + clusterId" :active="category == 'http'">HTTP</menu-item>
<menu-item :href="'/clusters/cluster/settings/metrics?category=tcp&clusterId=' + clusterId" :active="category == 'tcp'">TCP</menu-item>
<menu-item :href="'/clusters/cluster/settings/metrics?category=udp&clusterId=' + clusterId" :active="category == 'udp'">UDP</menu-item>
<span class="item disabled">|</span>
<menu-item @click.prevent="createItem">[添加指标]</menu-item>
<span class="item disabled">|</span>
<span class="item"><tip-icon content="在这里设置的指标,会自动应用到部署在当前集群上的所有服务上。"></tip-icon></span>
</first-menu>
<p class="comment" v-if="items.length == 0">暂时还没有设置指标。</p>
<table class="ui table celled selectable" v-if="items.length > 0">
<thead>
<tr>
<th>指标名称</th>
<th>统计对象</th>
<th>统计周期</th>
<th class="three wide">统计数值</th>
<th class="two wide">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="item in items">
<td>{{item.name}}</td>
<td>
<span v-if="item.keys != null" v-for="key in item.keys" class="ui label basic small">{{key}}</span>
</td>
<td>
{{item.period}} {{item.periodUnit}}
</td>
<td>
<span class="ui label small basic">{{item.value}}</span>
</td>
<td>
<label-on :v-is-on="item.isOn"></label-on>
</td>
<td>
<a href="" @click.prevent="deleteItem(item.id)">删除</a>
</td>
</tr>
</table>
</div>

View File

@@ -0,0 +1,30 @@
Tea.context(function () {
this.createItem = function () {
teaweb.popup(Tea.url(".createPopup", {
clusterId: this.clusterId
}), {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
},
width: "50em",
height: "25em"
})
}
this.deleteItem = function (itemId) {
let that = this
teaweb.confirm("确定要删除这个指标吗?", function () {
that.$post(".delete")
.params({
itemId: itemId
})
.success(function () {
teaweb.success("删除成功", function () {
teaweb.reload()
})
})
})
}
})

View File

@@ -12,7 +12,7 @@
<td class="title">是否启用TOA</td>
<td>
<checkbox name="isOn" v-model="toa.isOn"></checkbox>
<p class="comment">在启用之前,请确保当前集群下所有节点服务器已经安装TOA模块和libnetfilter_queue并启用了IPTables。</p>
<p class="comment">在启用之前请确保当前集群下所有节点服务器已经安装libnetfilter_queue并启用了IPTables。</p>
</td>
</tr>
<tbody v-show="toa.isOn">

View File

@@ -24,16 +24,37 @@
<!-- 准备工作 -->
<div v-show="step == 'prepare'">
<div style="margin-bottom: 1em">
<radio name="authType" :v-value="'http'" v-model="authType">使用HTTP认证</radio> &nbsp;
<radio name="authType" :v-value="'dns'" v-model="authType">使用DNS认证</radio>
</div>
<div v-if="authType == 'http'">
使用HTTP的方式请求网址<code-label>/.well-known/acme-challenge/令牌</code-label>校验,该方式要求需要实现将域名解析到集群上,之后系统会自动生成网址信息。
</div>
<div v-if="authType == 'dns'">
我们在申请免费证书的过程中需要自动增加或修改相关域名的TXT记录请先确保你已经在"域名解析" -- <a href="/dns/providers" target="_blank">"DNS服务商"</a> 中已经添加了对应的DNS服务商账号。
</div>
<table class="ui table definition selectable">
<tr>
<td class="title">认证方式</td>
<td>
<div style="margin-bottom: 1em">
<radio name="authType" :v-value="'http'" v-model="authType">使用HTTP认证</radio> &nbsp;
<radio name="authType" :v-value="'dns'" v-model="authType">使用DNS认证</radio>
</div>
<div v-if="authType == 'http'">
<p class="comment">使用HTTP的方式请求网址<code-label>/.well-known/acme-challenge/令牌</code-label>校验,该方式要求需要实现将域名解析到集群上,之后系统会自动生成网址信息。</p>
</div>
<div v-if="authType == 'dns'">
<p class="comment">我们在申请免费证书的过程中需要自动增加或修改相关域名的TXT记录请先确保你已经在"域名解析" -- <a href="/dns/providers" target="_blank">"DNS服务商"</a> 中已经添加了对应的DNS服务商账号。</p>
</div>
</td>
</tr>
<tr>
<td colspan="2">
<a href="" @click.prevent="showPrepareMoreOptionsVisible">更多选项<i class="icon angle" :class="{up: prepareMoreOptionsVisible, down: !prepareMoreOptionsVisible}"></i></a>
</td>
</tr>
<tbody v-show="prepareMoreOptionsVisible">
<tr>
<td>回调URL</td>
<td>
<input type="text" name="authURL" v-model="authURL" maxlength="200"/>
<p class="comment">将认证数据以JSON的方式POST到此URL上可以依此生成认证文件或者设置DNS域名解析。</p>
</td>
</tr>
</tbody>
</table>
<div class="button-group">
<button type="button" class="ui button primary" @click.prevent="doPrepare">下一步</button>

View File

@@ -10,6 +10,12 @@ Tea.context(function () {
this.step = "user"
}
this.prepareMoreOptionsVisible = false
this.authURL = ""
this.showPrepareMoreOptionsVisible = function () {
this.prepareMoreOptionsVisible = !this.prepareMoreOptionsVisible
}
/**
* 选择用户
*/
@@ -78,7 +84,8 @@ Tea.context(function () {
dnsDomain: this.dnsDomain,
domains: this.domains,
autoRenew: this.autoRenew ? 1 : 0,
taskId: this.taskId
taskId: this.taskId,
authURL: this.authURL
})
.success(function (resp) {
this.taskId = resp.data.taskId

View File

@@ -41,6 +41,18 @@
<p class="comment">在免费证书临近到期之前,是否尝试自动续期。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>回调URL</td>
<td>
<input type="text" name="authURL" v-model="task.authURL" maxlength="200"/>
<p class="comment">将认证数据以JSON的方式POST到此URL上可以依此生成认证文件或者设置DNS域名解析。</p>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>

View File

@@ -56,10 +56,13 @@ Tea.context(function () {
* 添加IP名单菜单
*/
this.createIP = function (type) {
let that = this
teaweb.popup("/servers/components/waf/ipadmin/createIPPopup?firewallPolicyId=" + this.firewallPolicyId + '&type=' + type, {
height: "23em",
callback: function () {
window.location = "/servers/components/waf/ipadmin/lists?firewallPolicyId=" + this.firewallPolicyId + "&type=" + type
teaweb.success("保存成功", function () {
window.location = "/servers/components/waf/ipadmin/lists?firewallPolicyId=" + that.firewallPolicyId + "&type=" + type
})
}
})
}

View File

@@ -4,6 +4,8 @@
<second-menu style="margin-top: -1em">
<span class="item">ID: {{listId}} &nbsp; <tip-icon content="ID可以用于使用API操作此IP名单"></tip-icon></span>
<span class="item disabled">|</span>
<div class="item"><ip-list-bind-box :v-http-firewall-policy-id="firewallPolicyId" :v-type="type"></ip-list-bind-box></div>
</second-menu>
<p class="comment" v-if="items.length == 0">暂时还没有IP。</p>

View File

@@ -26,10 +26,13 @@ Tea.context(function () {
* 添加IP名单菜单
*/
this.createIP = function (type) {
let that = this
teaweb.popup("/servers/components/waf/ipadmin/createIPPopup?firewallPolicyId=" + this.firewallPolicyId + '&type=' + type, {
height: "26em",
callback: function () {
window.location = "/servers/components/waf/ipadmin/lists?firewallPolicyId=" + this.firewallPolicyId + "&type=" + type
teaweb.success("保存成功", function () {
window.location = "/servers/components/waf/ipadmin/lists?firewallPolicyId=" + that.firewallPolicyId + "&type=" + type
})
}
})
}

View File

@@ -42,10 +42,13 @@ Tea.context(function () {
* 添加IP名单菜单
*/
this.createIP = function (type) {
let that = this
teaweb.popup("/servers/components/waf/ipadmin/createIPPopup?firewallPolicyId=" + this.firewallPolicyId + '&type=' + type, {
height: "23em",
callback: function () {
window.location = "/servers/components/waf/ipadmin/lists?firewallPolicyId=" + this.firewallPolicyId + "&type=" + type
teaweb.success("保存成功", function () {
window.location = "/servers/components/waf/ipadmin/lists?firewallPolicyId=" + that.firewallPolicyId + "&type=" + type
})
}
})
}

View File

@@ -1,47 +1,50 @@
Tea.context(function () {
this.ip = ""
this.result = {
isDone: false,
isOk: false,
isFound: false,
isAllowed: false,
error: "",
province: null,
country: null,
ipItem: null,
ipList: null
}
this.ip = ""
this.result = {
isDone: false,
isOk: false,
isFound: false,
isAllowed: false,
error: "",
province: null,
country: null,
ipItem: null,
ipList: null
}
this.$delay(function () {
this.$watch("ip", function () {
this.result.isDone = false
})
})
this.$delay(function () {
this.$watch("ip", function () {
this.result.isDone = false
})
})
this.success = function (resp) {
this.result = resp.data.result
}
this.success = function (resp) {
this.result = resp.data.result
}
this.updateItem = function (itemId) {
teaweb.popup(Tea.url(".updateIPPopup?firewallPolicyId=" + this.firewallPolicyId, {itemId: itemId}), {
height: "26em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.updateItem = function (itemId) {
teaweb.popup(Tea.url(".updateIPPopup?firewallPolicyId=" + this.firewallPolicyId, {itemId: itemId}), {
height: "26em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
/**
* 添加IP名单菜单
*/
this.createIP = function (type) {
teaweb.popup("/servers/components/waf/ipadmin/createIPPopup?firewallPolicyId=" + this.firewallPolicyId + '&type=' + type, {
height: "26em",
callback: function () {
window.location = "/servers/components/waf/ipadmin/lists?firewallPolicyId=" + this.firewallPolicyId + "&type=" + type
}
})
}
/**
* 添加IP名单菜单
*/
this.createIP = function (type) {
let that = this
teaweb.popup("/servers/components/waf/ipadmin/createIPPopup?firewallPolicyId=" + this.firewallPolicyId + '&type=' + type, {
height: "26em",
callback: function () {
teaweb.success("保存成功", function () {
window.location = "/servers/components/waf/ipadmin/lists?firewallPolicyId=" + that.firewallPolicyId + "&type=" + type
})
}
})
}
})

View File

@@ -41,6 +41,7 @@
<td>绑定域名</td>
<td>
<server-name-box></server-name-box>
<p class="comment">绑定后,才能通过域名可以访问不同的服务。</p>
</td>
</tr>

View File

@@ -106,7 +106,7 @@
<td>
<span v-if="server.ports.length == 0">-</span>
<div v-for="port in server.ports">
<tiny-basic-label>{{port.portRange}}<span class="small">{{port.protocol}}</span></tiny-basic-label>
<tiny-basic-label><keyword :v-word="keyword">{{port.portRange}}</keyword><span class="small">{{port.protocol}}</span></tiny-basic-label>
</div>
</td>
<td class="center">

View File

@@ -0,0 +1,10 @@
<first-menu>
<menu-item :href="'/servers/iplists?type=' + list.type">{{list.typeName}}</menu-item>
<span class="disabled item">|</span>
<menu-item :href="'/servers/iplists/list?listId=' + list.id" code="list">"{{list.name}}"详情</menu-item>
<menu-item :href="'/servers/iplists/items?listId=' + list.id" code="item">IP({{list.countItems}})</menu-item>
<menu-item :href="'/servers/iplists/update?listId=' + list.id" code="update">修改</menu-item>
<menu-item :href="'/servers/iplists/test?listId=' + list.id" code="test">IP检查</menu-item>
<menu-item :href="'/servers/iplists/export?listId=' + list.id" code="export">导出</menu-item>
<menu-item :href="'/servers/iplists/import?listId=' + list.id" code="import">导入</menu-item>
</first-menu>

View File

@@ -0,0 +1,8 @@
<first-menu>
<menu-item href="/servers/iplists" :active="type == 'black'">黑名单</menu-item>
<menu-item href="/servers/iplists?type=white" :active="type == 'white'">白名单</menu-item>
<span class="item disabled">|</span>
<menu-item @click.prevent="createList">[创建]</menu-item>
<span class="item disabled">|</span>
<span class="item"><tip-icon content="可以在WAF策略里直接引用这些公用名单。"></tip-icon></span>
</first-menu>

View File

@@ -0,0 +1,38 @@
{$layout "layout_popup"}
<h3>绑定公用IP名单</h3>
<p class="comment" v-if="lists.length == 0">暂时还没有可用的公用IP名单。</p>
<table class="ui table selectable celled" v-if="lists.length > 0">
<thead>
<tr>
<th class="two wide">ID</th>
<th>名称</th>
<th class="two wide">类型</th>
<th>备注</th>
<th class="two wide">IP数量</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="list in lists">
<td>{{list.id}}</td>
<td>{{list.name}}</td>
<td>
<span v-if="list.type == 'black'">黑名单</span>
<span v-if="list.type == 'white'">白名单</span>
</td>
<td>{{list.description}}</td>
<td>
<span v-if="list.countItems > 0">{{list.countItems}}</span>
<span v-else class="disabled">0</span>
</td>
<td>
<a href="" @click.prevent="bind(list)" v-if="!list.isSelected">绑定</a>
<a href="" style="color: grey" @click.prevent="unbind(list)" v-if="list.isSelected">已绑定</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,23 @@
Tea.context(function () {
this.bind = function (list) {
this.$post("$")
.params({
httpFirewallPolicyId: this.httpFirewallPolicyId,
listId: list.id
})
.success(function () {
list.isSelected = true
})
}
this.unbind = function (list) {
this.$post(".unbindHTTPFirewall")
.params({
httpFirewallPolicyId: this.httpFirewallPolicyId,
listId: list.id
})
.success(function () {
list.isSelected = false
})
}
})

View File

@@ -0,0 +1,76 @@
{$layout "layout_popup"}
<h3>添加IP</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="listId" :value="listId"/>
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">类型 *</td>
<td>
<select class="ui dropdown auto-width" name="type" v-model="type">
<option value="ipv4">IPv4</option>
<option value="ipv6">IPv6</option>
<option value="all">所有IP</option>
</select>
<p class="comment" v-if="type == 'ipv4'">单个IPv4或一个IPv4范围。</p>
<p class="comment" v-if="type == 'ipv6'">单个IPv6。</p>
<p class="comment" v-if="type == 'all'">允许或禁用所有的IP。</p>
</td>
</tr>
<!-- IPv4 -->
<tbody v-if="type == 'ipv4'">
<tr>
<td>开始IP *</td>
<td>
<input type="text" name="ipFrom" maxlength="64" placeholder="x.x.x.x" ref="focus"/>
</td>
</tr>
<tr>
<td>结束IP</td>
<td>
<input type="text" name="ipTo" maxlength="64" placeholder="x.x.x.x"/>
<p class="comment">表示IP段的时候需要填写此项。</p>
</td>
</tr>
</tbody>
<!-- IPv6 -->
<tbody v-if="type == 'ipv6'">
<tr>
<td>IP *</td>
<td>
<input type="text" name="ipFrom" maxlength="64" placeholder="x:x:x:x:x:x:x:x" ref="focus"/>
<p class="comment">IPv6地址比如 1406:3c00:0:2409:13:58:103:15</p>
</td>
</tr>
</tbody>
<tr>
<td>级别</td>
<td>
<firewall-event-level-options :v-value="eventLevel"></firewall-event-level-options>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>过期时间</td>
<td>
<datetime-input :v-name="'expiredAt'"></datetime-input>
<p class="comment">在加入名单某一段时间后会失效,留空表示永久有效。</p>
</td>
</tr>
<tr>
<td>备注</td>
<td><input type="text" name="reason" maxlength="100"/></td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,4 @@
Tea.context(function () {
this.type = "ipv4"
this.eventLevel = (this.listType == "white") ? "debug" : "critical"
})

View File

@@ -0,0 +1,4 @@
h3 var {
font-style: normal;
}
/*# sourceMappingURL=createPopup.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["createPopup.less"],"names":[],"mappings":"AAAA,EACC;EACC,kBAAA","file":"createPopup.css"}

View File

@@ -0,0 +1,31 @@
{$layout "layout_popup"}
<h3>创建<var v-if="type == 'black'">黑名单</var><var v-if="type == 'white'">白名单</var></h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus"/>
</td>
</tr>
<tr>
<td>类型</td>
<td>
<select class="ui dropdown auto-width" name="type" v-model="type">
<option value="black">黑名单</option>
<option value="white">白名单</option>
</select>
</td>
</tr>
<tr>
<td>备注</td>
<td>
<textarea name="description" rows="2" maxlength="200"></textarea>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,5 @@
h3 {
var {
font-style: normal;
}
}

View File

@@ -0,0 +1,13 @@
{$layout}
{$template "list_menu"}
<div class="margin"></div>
<form class="ui form">
<table class="ui table definition selectable">
<tr>
<td class="title">说明</td>
<td>导出所有的IP</td>
</tr>
</table>
<a :href="'/servers/iplists/exportData?listId=' + list.id" class="ui button primary">导出</a>
</form>

View File

@@ -0,0 +1,19 @@
{$layout}
{$template "list_menu"}
<div class="margin"></div>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="listId" :value="list.id"/>
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">选择IP文件 *</td>
<td>
<input type="file" name="file" accept=".data"/>
<p class="comment">文件名类似于<code-label>ip-list-123.data</code-label></p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,7 @@
Tea.context(function () {
this.success = function (resp) {
teaweb.success("成功导入" + resp.data.count + "个IP", function () {
teaweb.reload()
})
}
})

View File

@@ -0,0 +1,38 @@
{$layout}
{$template "menu"}
<tip-message-box code="iplist-public-tip">这里是公用的IP名单可以在WAF策略里直接引用。</tip-message-box>
<p class="comment" v-if="lists.length == 0">暂时还没有公用IP名单。</p>
<table class="ui table selectable celled" v-if="lists.length > 0">
<thead>
<tr>
<th class="one wide">ID</th>
<th>名称</th>
<th class="one wide">类型</th>
<th>备注</th>
<th class="one wide">IP数量</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="list in lists">
<td>{{list.id}}</td>
<td>{{list.name}}</td>
<td>
<span v-if="list.type == 'black'">黑名单</span>
<span v-if="list.type == 'white'">白名单</span>
</td>
<td>{{list.description}}</td>
<td>
<span v-if="list.countItems > 0">{{list.countItems}}</span>
<span v-else class="disabled">0</span>
</td>
<td>
<a :href="'/servers/iplists/list?listId=' + list.id">详情</a> &nbsp;
<a href="" @click.prevent="deleteList(list.id)">删除</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,26 @@
Tea.context(function () {
this.createList = function () {
teaweb.popup(Tea.url(".createPopup", {type: this.type}), {
callback: function (resp) {
teaweb.success("保存成功", function () {
window.location = "/servers/iplists?type=" + resp.data.list.type
})
}
})
}
this.deleteList = function (listId) {
let that = this
teaweb.confirm("确定要删除此IP名单吗", function () {
that.$post(".delete")
.params({
listId: listId
})
.success(function () {
teaweb.success("删除成功", function () {
teaweb.reload()
})
})
})
}
})

View File

@@ -0,0 +1,12 @@
{$layout}
{$template "list_menu"}
<second-menu>
<menu-item @click.prevent="createIP">[创建IP]</menu-item>
</second-menu>
<p class="comment" v-if="items.length == 0">暂时还没有IP。</p>
<ip-list-table v-if="items.length > 0" :v-items="items" @update-item="updateItem" @delete-item="deleteItem"></ip-list-table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,37 @@
Tea.context(function () {
this.updateItem = function (itemId) {
teaweb.popup(Tea.url(".updateIPPopup", {itemId: itemId}), {
height: "26em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deleteItem = function (itemId) {
let that = this
teaweb.confirm("确定要删除这个IP吗", function () {
that.$post(".deleteIP")
.params({
"itemId": itemId
})
.refresh()
})
}
/**
* 添加IP名单菜单
*/
this.createIP = function () {
teaweb.popup(Tea.url(".createIPPopup", {listId: this.list.id}), {
height: "23em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
})

View File

@@ -0,0 +1,24 @@
{$layout}
{$template "list_menu"}
<table class="ui table definition selectable">
<tr>
<td class="title">名称</td>
<td>
{{list.name}}
</td>
</tr>
<tr>
<td>类型</td>
<td>
{{list.typeName}}
</td>
</tr>
<tr>
<td>备注</td>
<td>
<span v-if="list.description.length > 0">{{list.description}}</span>
<span v-else class="disabled">-</span>
</td>
</tr>
</table>

View File

@@ -0,0 +1,39 @@
{$layout}
{$template "list_menu"}
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="listId" :value="list.id"/>
<table class="ui table selectable definition">
<tr>
<td class="title">IP *</td>
<td>
<input type="text" name="ip" class="text" maxlength="100" ref="focus" placeholder="x.x.x.x" v-model="ip"/>
<p class="comment">要检查的IP</p>
</td>
</tr>
<tr>
<td>检查结果</td>
<td>
<div v-if="result.isDone">
<div v-if="!result.isOk">
<span class="red">{{result.error}}</span>
</div>
<div v-if="result.isFound">
<div v-if="result.item != null">
<div v-if="result.isAllowed">
<span class="green">在白名单中 <ip-item-text :v-item="result.item"></ip-item-text>&nbsp;<a href="" @click.prevent="updateItem(result.list.id, result.item.id)" title="查看和修改"><i class="icon pencil small"></i></a></span>
</div>
<div v-else>
<span class="red">在黑名单中 <ip-item-text :v-item="result.item"></ip-item-text>&nbsp;<a href="" @click.prevent="updateItem(result.item.id)" title="查看和修改"><i class="icon pencil small"></i></a></span>
</div>
</div>
</div>
<div v-if="!result.isFound">
没有找到和{{ip}}匹配的配置。
</div>
</div>
</td>
</tr>
</table>
<submit-btn>检查IP状态</submit-btn>
</form>

View File

@@ -0,0 +1,33 @@
Tea.context(function () {
this.ip = ""
this.result = {
isDone: false,
isOk: false,
isFound: false,
isAllowed: false,
error: "",
ipItem: null,
ipList: null
}
this.$delay(function () {
this.$watch("ip", function () {
this.result.isDone = false
})
})
this.success = function (resp) {
this.result = resp.data.result
}
this.updateItem = function (itemId) {
teaweb.popup(Tea.url(".updateIPPopup", {itemId: itemId}), {
height: "26em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
})

View File

@@ -0,0 +1,28 @@
{$layout}
{$template "list_menu"}
<form class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="listId" :value="list.id"/>
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus" v-model="list.name"/>
</td>
</tr>
<tr>
<td>类型</td>
<td>
{{list.typeName}}
</td>
</tr>
<tr>
<td>备注</td>
<td>
<textarea name="description" rows="2" maxlength="200" v-model="list.description"></textarea>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,3 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
})

View File

@@ -0,0 +1,76 @@
{$layout "layout_popup"}
<h3>修改IP</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="itemId" :value="item.id"/>
<input type="hidden" name="type" :value="item.type"/>
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">类型 *</td>
<td>
<!-- 类型不允许修改 -->
<span v-if="item.type == 'ipv4'">IPv4</span>
<span v-if="item.type == 'ipv6'">IPv6</span>
<span v-if="item.type == 'all'">所有IP</span>
<p class="comment" v-if="type == 'ipv4'">单个IPv4或一个IPv4范围。</p>
<p class="comment" v-if="type == 'ipv6'">单个IPv6。</p>
<p class="comment" v-if="type == 'all'">允许或禁用所有的IP。</p>
</td>
</tr>
<!-- IPv4 -->
<tbody v-if="type == 'ipv4'">
<tr>
<td>开始IP *</td>
<td>
<input type="text" name="ipFrom" maxlength="64" placeholder="x.x.x.x" ref="focus" v-model="item.ipFrom"/>
</td>
</tr>
<tr>
<td>结束IP</td>
<td>
<input type="text" name="ipTo" maxlength="64" placeholder="x.x.x.x" v-model="item.ipTo"/>
<p class="comment">表示IP段的时候需要填写此项。</p>
</td>
</tr>
</tbody>
<tr>
<td>级别</td>
<td>
<firewall-event-level-options :v-value="item.eventLevel"></firewall-event-level-options>
</td>
</tr>
<!-- IPv6 -->
<tbody v-if="type == 'ipv6'">
<tr>
<td>IP *</td>
<td>
<input type="text" name="ipFrom" maxlength="64" placeholder="x:x:x:x:x:x:x:x" ref="focus" v-model="item.ipFrom"/>
<p class="comment">IPv6地址比如 1406:3c00:0:2409:13:58:103:15</p>
</td>
</tr>
</tbody>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>过期时间</td>
<td>
<datetime-input :v-name="'expiredAt'" :v-timestamp="item.expiredAt"></datetime-input>
<p class="comment">在加入名单某一段时间后会失效,留空表示永久有效。</p>
</td>
</tr>
<tr>
<td>备注</td>
<td><input type="text" name="reason" maxlength="100" v-model="item.reason"/></td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,7 @@
<first-menu>
<menu-item :href="'/servers/metrics'">指标列表</menu-item>
<span class="item disabled">|</span>
<menu-item :href="'/servers/metrics/item?itemId=' + item.id" code="item">"{{item.name}}"详情</menu-item>
<menu-item :href="'/servers/metrics/update?itemId=' + item.id" code="update">修改</menu-item>
<menu-item :href="'/servers/metrics/charts?itemId=' + item.id" code="chart">图表</menu-item>
</first-menu>

View File

@@ -0,0 +1,4 @@
{$layout}
{$template "item_menu"}
<p class="ui message warning">此功能暂未开通,敬等期待。</p>

View File

@@ -0,0 +1,54 @@
{$layout "layout_popup"}
<h3>创建{{category.toUpperCase()}}统计指标</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="category" :value="category"/>
<table class="ui table definition selectable">
<tr>
<td class="title">指标名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus"/>
</td>
</tr>
<tr>
<td>统计对象 *</td>
<td>
<metric-keys-config-box :v-category="category"></metric-keys-config-box>
</td>
</tr>
<tr>
<td>统计周期 *</td>
<td>
<metric-period-config-box></metric-period-config-box>
</td>
</tr>
<tr>
<td>统计数值 *</td>
<td>
<!-- HTTP -->
<select class="ui dropdown auto-width" name="value" v-if="category == 'http'">
<option value="${countRequest}">请求数</option>
<option value="${countConnection}">连接数</option>
<option value="${countTrafficIn}">下行流量</option>
<option value="${countTrafficOut}">上行流量</option>
</select>
<!-- TCP -->
<select class="ui dropdown auto-width" name="value" v-if="category == 'tcp'">
<option value="${countConnection}">连接数</option>
<option value="${countTrafficIn}">下行流量</option>
<option value="${countTrafficOut}">上行流量</option>
</select>
<!-- UDP -->
<select class="ui dropdown auto-width" name="value" v-if="category == 'udp'">
<option value="${countConnection}">连接数</option>
<option value="${countTrafficIn}">下行流量</option>
<option value="${countTrafficOut}">上行流量</option>
</select>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

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