实现公用的IP名单

This commit is contained in:
GoEdgeLab
2021-06-23 13:12:33 +08:00
parent c4f7eadeda
commit ee2420e8ad
88 changed files with 2060 additions and 69 deletions

View File

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

View File

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

View File

@@ -0,0 +1,139 @@
// 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
}) {
// 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,58 @@
// 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
}) {
// 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,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("/selectProvincesPopup", new(SelectProvincesPopupAction)).
GetPost("/selectCountriesPopup", new(SelectCountriesPopupAction)). GetPost("/selectCountriesPopup", new(SelectCountriesPopupAction)).
Post("/eventLevelOptions", new(EventLevelOptionsAction)). Post("/eventLevelOptions", new(EventLevelOptionsAction)).
Post("/showTip", new(ShowTipAction)).
Post("/hideTip", new(HideTipAction)).
EndAll() 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,6 +167,11 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
"url": "/servers/components/waf", "url": "/servers/components/waf",
"code": "waf", "code": "waf",
}, },
{
"name": "IP名单",
"url": "/servers/iplists",
"code": "iplist",
},
{ {
"name": "证书管理", "name": "证书管理",
"url": "/servers/certs", "url": "/servers/certs",

View File

@@ -109,6 +109,8 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/websocket" _ "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/server/stat"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/iplists"
// 设置相关 // 设置相关
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/authority" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/authority"

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() this.select()
teaweb.popup("/servers/server/log/viewPopup?requestId=" + requestId, { teaweb.popup("/servers/server/log/viewPopup?requestId=" + requestId, {
width: "50em", width: "50em",
height: "24em", height: "28em",
onClose: function () { onClose: function () {
that.deselect() that.deselect()
} }

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -80,7 +80,7 @@
<span v-if="module.code.length > 0"> <span v-if="module.code.length > 0">
<i class="window restore outline icon" v-if="module.icon == null"></i> <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> <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> </span>
<div class="subtitle" v-if="module.subtitle != null && module.subtitle.length > 0">{{module.subtitle}}</div> <div class="subtitle" v-if="module.subtitle != null && module.subtitle.length > 0">{{module.subtitle}}</div>
</a> </a>

View File

@@ -139,7 +139,7 @@ div.margin, p.margin {
} }
.main-menu .ui.menu .item span { .main-menu .ui.menu .item span {
display: none;
} }
} }
@@ -163,11 +163,23 @@ div.margin, p.margin {
color: grey; color: grey;
} }
@media screen and (max-width: 512px) {
.item.expend .subtitle {
display: none;
}
}
.sub-items { .sub-items {
.item { .item {
padding-left: 2.8em !important; padding-left: 2.8em !important;
} }
@media screen and (max-width: 512px) {
.item {
padding-left: 1em!important;
}
}
.item.active { .item.active {
background-color: #2185d0 !important; background-color: #2185d0 !important;
} }

View File

@@ -56,10 +56,13 @@ Tea.context(function () {
* 添加IP名单菜单 * 添加IP名单菜单
*/ */
this.createIP = function (type) { this.createIP = function (type) {
let that = this
teaweb.popup("/servers/components/waf/ipadmin/createIPPopup?firewallPolicyId=" + this.firewallPolicyId + '&type=' + type, { teaweb.popup("/servers/components/waf/ipadmin/createIPPopup?firewallPolicyId=" + this.firewallPolicyId + '&type=' + type, {
height: "23em", height: "23em",
callback: function () { 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"> <second-menu style="margin-top: -1em">
<span class="item">ID: {{listId}} &nbsp; <tip-icon content="ID可以用于使用API操作此IP名单"></tip-icon></span> <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> </second-menu>
<p class="comment" v-if="items.length == 0">暂时还没有IP。</p> <p class="comment" v-if="items.length == 0">暂时还没有IP。</p>

View File

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

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

@@ -11,6 +11,8 @@
<menu-item @click.prevent="createIP('white')">添加IP</menu-item> <menu-item @click.prevent="createIP('white')">添加IP</menu-item>
<span class="item">|</span> <span class="item">|</span>
<span class="item">ID: {{listId}} &nbsp; <tip-icon content="ID可以用于使用API操作此IP名单"></tip-icon></span> <span class="item">ID: {{listId}} &nbsp; <tip-icon content="ID可以用于使用API操作此IP名单"></tip-icon></span>
<span class="item">|</span>
<div class="item"><ip-list-bind-box :v-http-firewall-policy-id="firewallPolicyId" :v-type="'white'"></ip-list-bind-box></div>
</second-menu> </second-menu>
<p class="ui message warning" v-if="!wafIsOn">当前WAF未启用设置将在<a :href="'/servers/server/settings/waf?serverId=' + serverId">[启用]</a>后生效。</p> <p class="ui message warning" v-if="!wafIsOn">当前WAF未启用设置将在<a :href="'/servers/server/settings/waf?serverId=' + serverId">[启用]</a>后生效。</p>

View File

@@ -29,7 +29,9 @@ Tea.context(function () {
teaweb.popup("/servers/server/settings/waf/ipadmin/createIPPopup?listId=" + this.listId + '&type=' + type, { teaweb.popup("/servers/server/settings/waf/ipadmin/createIPPopup?listId=" + this.listId + '&type=' + type, {
height: "26em", height: "26em",
callback: function () { callback: function () {
window.location.reload() teaweb.success("保存成功", function () {
teaweb.reload()
})
} }
}) })
} }

View File

@@ -56,10 +56,13 @@ Tea.context(function () {
* 添加IP名单菜单 * 添加IP名单菜单
*/ */
this.createIP = function (type) { this.createIP = function (type) {
let that = this
teaweb.popup("/servers/components/waf/ipadmin/createIPPopup?firewallPolicyId=" + this.firewallPolicyId + '&type=' + type, { teaweb.popup("/servers/components/waf/ipadmin/createIPPopup?firewallPolicyId=" + this.firewallPolicyId + '&type=' + type, {
height: "30em", height: "30em",
callback: function () { 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

@@ -11,6 +11,8 @@
<menu-item @click.prevent="createIP('black')">添加IP</menu-item> <menu-item @click.prevent="createIP('black')">添加IP</menu-item>
<span class="item">|</span> <span class="item">|</span>
<span class="item">ID: {{listId}} &nbsp; <tip-icon content="ID可以用于使用API操作此IP名单"></tip-icon></span> <span class="item">ID: {{listId}} &nbsp; <tip-icon content="ID可以用于使用API操作此IP名单"></tip-icon></span>
<span class="item">|</span>
<div class="item"><ip-list-bind-box :v-http-firewall-policy-id="firewallPolicyId" :v-type="'black'"></ip-list-bind-box></div>
</second-menu> </second-menu>
<p class="ui message warning" v-if="!wafIsOn">当前WAF未启用设置将在<a :href="'/servers/server/settings/waf?serverId=' + serverId">[启用]</a>后生效。</p> <p class="ui message warning" v-if="!wafIsOn">当前WAF未启用设置将在<a :href="'/servers/server/settings/waf?serverId=' + serverId">[启用]</a>后生效。</p>

View File

@@ -29,7 +29,9 @@ Tea.context(function () {
teaweb.popup("/servers/server/settings/waf/ipadmin/createIPPopup?listId=" + this.listId + '&type=' + type, { teaweb.popup("/servers/server/settings/waf/ipadmin/createIPPopup?listId=" + this.listId + '&type=' + type, {
height: "26em", height: "26em",
callback: function () { callback: function () {
window.location.reload() teaweb.success("保存成功", function () {
teaweb.reload()
})
} }
}) })
} }

View File

@@ -42,10 +42,13 @@ Tea.context(function () {
* 添加IP名单菜单 * 添加IP名单菜单
*/ */
this.createIP = function (type) { this.createIP = function (type) {
let that = this
teaweb.popup("/servers/components/waf/ipadmin/createIPPopup?firewallPolicyId=" + this.firewallPolicyId + '&type=' + type, { teaweb.popup("/servers/components/waf/ipadmin/createIPPopup?firewallPolicyId=" + this.firewallPolicyId + '&type=' + type, {
height: "30em", height: "30em",
callback: function () { 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,4 +1,7 @@
.chart-box { .chart-box {
height: 20em; height: 20em;
} }
h4 span.small {
font-size: 0.8em;
}
/*# sourceMappingURL=clients.css.map */ /*# sourceMappingURL=clients.css.map */

View File

@@ -1 +1 @@
{"version":3,"sources":["clients.less"],"names":[],"mappings":"AAAA;EACC,YAAA","file":"clients.css"} {"version":3,"sources":["clients.less"],"names":[],"mappings":"AAAA;EACC,YAAA;;AAGD,EAAG,KAAI;EACN,gBAAA","file":"clients.css"}

View File

@@ -12,10 +12,10 @@
要想查看统计数据,需要先开启统计功能,<a :href="'/servers/server/settings/stat?serverId=' + serverId">[点击这里]</a>修改配置。 要想查看统计数据,需要先开启统计功能,<a :href="'/servers/server/settings/stat?serverId=' + serverId">[点击这里]</a>修改配置。
</p> </p>
{$else} {$else}
<h4>操作系统排行</h4> <h4>操作系统排行<span class="small grey">(按月)</span></h4>
<div class="chart-box" id="system-chart"></div> <div class="chart-box" id="system-chart"></div>
<h4>浏览器排行</h4> <h4>浏览器排行<span class="small grey">(按月)</span></h4>
<div class="chart-box" id="browser-chart"></div> <div class="chart-box" id="browser-chart"></div>
{$end} {$end}
</div> </div>

View File

@@ -1,3 +1,7 @@
.chart-box { .chart-box {
height: 20em; height: 20em;
}
h4 span.small {
font-size: 0.8em;
} }

View File

@@ -1,4 +1,7 @@
.chart-box { .chart-box {
height: 21em; height: 21em;
} }
h4 span.small {
font-size: 0.8em;
}
/*# sourceMappingURL=dailyRequests.css.map */ /*# sourceMappingURL=dailyRequests.css.map */

View File

@@ -1 +1 @@
{"version":3,"sources":["dailyRequests.less"],"names":[],"mappings":"AAAA;EACC,YAAA","file":"dailyRequests.css"} {"version":3,"sources":["dailyRequests.less"],"names":[],"mappings":"AAAA;EACC,YAAA;;AAGD,EAAG,KAAI;EACN,gBAAA","file":"dailyRequests.css"}

View File

@@ -1,3 +1,7 @@
.chart-box { .chart-box {
height: 21em; height: 21em;
}
h4 span.small {
font-size: 0.8em;
} }

View File

@@ -1,4 +1,7 @@
.chart-box { .chart-box {
height: 21em; height: 21em;
} }
h4 span.small {
font-size: 0.8em;
}
/*# sourceMappingURL=hourlyRequests.css.map */ /*# sourceMappingURL=hourlyRequests.css.map */

View File

@@ -1 +1 @@
{"version":3,"sources":["hourlyRequests.less"],"names":[],"mappings":"AAAA;EACC,YAAA","file":"hourlyRequests.css"} {"version":3,"sources":["hourlyRequests.less"],"names":[],"mappings":"AAAA;EACC,YAAA;;AAGD,EAAG,KAAI;EACN,gBAAA","file":"hourlyRequests.css"}

View File

@@ -1,3 +1,7 @@
.chart-box { .chart-box {
height: 21em; height: 21em;
}
h4 span.small {
font-size: 0.8em;
} }

View File

@@ -1,4 +1,7 @@
.chart-box { .chart-box {
height: 21em; height: 21em;
} }
h4 span.small {
font-size: 0.8em;
}
/*# sourceMappingURL=index.css.map */ /*# sourceMappingURL=index.css.map */

View File

@@ -1 +1 @@
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA;EACC,YAAA","file":"index.css"} {"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA;EACC,YAAA;;AAGD,EAAG,KAAI;EACN,gBAAA","file":"index.css"}

View File

@@ -1,3 +1,7 @@
.chart-box { .chart-box {
height: 21em; height: 21em;
}
h4 span.small {
font-size: 0.8em;
} }

View File

@@ -1,4 +1,7 @@
.chart-box { .chart-box {
height: 20em; height: 20em;
} }
h4 span.small {
font-size: 0.8em;
}
/*# sourceMappingURL=providers.css.map */ /*# sourceMappingURL=providers.css.map */

View File

@@ -1 +1 @@
{"version":3,"sources":["providers.less"],"names":[],"mappings":"AAAA;EACC,YAAA","file":"providers.css"} {"version":3,"sources":["providers.less"],"names":[],"mappings":"AAAA;EACC,YAAA;;AAGD,EAAG,KAAI;EACN,gBAAA","file":"providers.css"}

View File

@@ -12,7 +12,7 @@
要想查看统计数据,需要先开启统计功能,<a :href="'/servers/server/settings/stat?serverId=' + serverId">[点击这里]</a>修改配置。 要想查看统计数据,需要先开启统计功能,<a :href="'/servers/server/settings/stat?serverId=' + serverId">[点击这里]</a>修改配置。
</p> </p>
{$else} {$else}
<h4>运营商排行</h4> <h4>运营商排行<span class="small grey">(按月)</span></h4>
<div class="chart-box" id="provider-chart"></div> <div class="chart-box" id="provider-chart"></div>
{$end} {$end}
</div> </div>

View File

@@ -1,3 +1,7 @@
.chart-box { .chart-box {
height: 20em; height: 20em;
}
h4 span.small {
font-size: 0.8em;
} }

View File

@@ -1,4 +1,7 @@
.chart-box { .chart-box {
height: 20em; height: 20em;
} }
/*# sourceMappingURL=index.css.map */ h4 span.small {
font-size: 0.8em;
}
/*# sourceMappingURL=regions.css.map */

View File

@@ -1 +1 @@
{"version":3,"sources":["index.less"],"names":[],"mappings":"AAAA;EACC,YAAA","file":"index.css"} {"version":3,"sources":["regions.less"],"names":[],"mappings":"AAAA;EACC,YAAA;;AAGD,EAAG,KAAI;EACN,gBAAA","file":"regions.css"}

View File

@@ -12,17 +12,17 @@
要想查看统计数据,需要先开启统计功能,<a :href="'/servers/server/settings/stat?serverId=' + serverId">[点击这里]</a>修改配置。 要想查看统计数据,需要先开启统计功能,<a :href="'/servers/server/settings/stat?serverId=' + serverId">[点击这里]</a>修改配置。
</p> </p>
{$else} {$else}
<h4>地区排行</h4> <h4>地区排行<span class="small grey">(按月)</span></h4>
<div class="chart-box" id="country-chart"> <div class="chart-box" id="country-chart">
</div> </div>
<h4>省市排行</h4> <h4>省市排行<span class="small grey">(按月)</span></h4>
<div class="chart-box" id="province-chart"> <div class="chart-box" id="province-chart">
</div> </div>
<h4>城市排行</h4> <h4>城市排行<span class="small grey">(按月)</span></h4>
<div class="chart-box" id="city-chart"> <div class="chart-box" id="city-chart">
</div> </div>

View File

@@ -1,3 +1,7 @@
.chart-box { .chart-box {
height: 20em; height: 20em;
}
h4 span.small {
font-size: 0.8em;
} }