diff --git a/go.mod b/go.mod index e6a71321..fdc69004 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ replace github.com/TeaOSLab/EdgeCommon => ../EdgeCommon require ( github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000 + 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/iwind/TeaGo v0.0.0-20201120063500-ee2d7090f4bc diff --git a/go.sum b/go.sum index ae30e8d6..acd284d8 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,11 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -91,6 +94,7 @@ github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk= github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= diff --git a/internal/ttlcache/cache.go b/internal/ttlcache/cache.go new file mode 100644 index 00000000..918bc232 --- /dev/null +++ b/internal/ttlcache/cache.go @@ -0,0 +1,151 @@ +package ttlcache + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/utils" + "time" +) + +var DefaultCache = NewCache() + +// TTL缓存 +// 最大的缓存时间为30 * 86400 +// Piece数据结构: +// Piece1 | Piece2 | Piece3 | ... +// [ Item1, Item2, ... | ... +// KeyMap列表数据结构 +// { timestamp1 => [key1, key2, ...] }, ... +type Cache struct { + isDestroyed bool + pieces []*Piece + countPieces uint64 + maxItems int + + gcPieceIndex int + ticker *utils.Ticker +} + +func NewCache(opt ...OptionInterface) *Cache { + countPieces := 128 + maxItems := 1_000_000 + for _, option := range opt { + if option == nil { + continue + } + switch o := option.(type) { + case *PiecesOption: + if o.Count > 0 { + countPieces = o.Count + } + case *MaxItemsOption: + if o.Count > 0 { + maxItems = o.Count + } + } + } + + cache := &Cache{ + countPieces: uint64(countPieces), + maxItems: maxItems, + } + + for i := 0; i < countPieces; i++ { + cache.pieces = append(cache.pieces, NewPiece(maxItems/countPieces)) + } + + // start timer + go func() { + cache.ticker = utils.NewTicker(5 * time.Second) + for cache.ticker.Next() { + cache.GC() + } + }() + + return cache +} + +func (this *Cache) Write(key string, value interface{}, expiredAt int64) { + if this.isDestroyed { + return + } + + currentTimestamp := time.Now().Unix() + if expiredAt <= currentTimestamp { + return + } + + maxExpiredAt := currentTimestamp + 30*86400 + if expiredAt > maxExpiredAt { + expiredAt = maxExpiredAt + } + uint64Key := HashKey([]byte(key)) + pieceIndex := uint64Key % this.countPieces + this.pieces[pieceIndex].Add(uint64Key, &Item{ + Value: value, + expiredAt: expiredAt, + }) +} + +func (this *Cache) IncreaseInt64(key string, delta int64, expiredAt int64) int64 { + if this.isDestroyed { + return 0 + } + + currentTimestamp := time.Now().Unix() + if expiredAt <= currentTimestamp { + return 0 + } + + maxExpiredAt := currentTimestamp + 30*86400 + if expiredAt > maxExpiredAt { + expiredAt = maxExpiredAt + } + uint64Key := HashKey([]byte(key)) + pieceIndex := uint64Key % this.countPieces + return this.pieces[pieceIndex].IncreaseInt64(uint64Key, delta, expiredAt) +} + +func (this *Cache) Read(key string) (item *Item) { + uint64Key := HashKey([]byte(key)) + return this.pieces[uint64Key%this.countPieces].Read(uint64Key) +} + +func (this *Cache) readIntKey(key uint64) (value *Item) { + return this.pieces[key%this.countPieces].Read(key) +} + +func (this *Cache) Delete(key string) { + uint64Key := HashKey([]byte(key)) + this.pieces[uint64Key%this.countPieces].Delete(uint64Key) +} + +func (this *Cache) deleteIntKey(key uint64) { + this.pieces[key%this.countPieces].Delete(key) +} + +func (this *Cache) Count() (count int) { + for _, piece := range this.pieces { + count += piece.Count() + } + return +} + +func (this *Cache) GC() { + this.pieces[this.gcPieceIndex].GC() + newIndex := this.gcPieceIndex + 1 + if newIndex >= int(this.countPieces) { + newIndex = 0 + } + this.gcPieceIndex = newIndex +} + +func (this *Cache) Destroy() { + this.isDestroyed = true + + if this.ticker != nil { + this.ticker.Stop() + this.ticker = nil + } + for _, piece := range this.pieces { + piece.Destroy() + } +} diff --git a/internal/ttlcache/cache_test.go b/internal/ttlcache/cache_test.go new file mode 100644 index 00000000..0ffcf118 --- /dev/null +++ b/internal/ttlcache/cache_test.go @@ -0,0 +1,124 @@ +package ttlcache + +import ( + "github.com/iwind/TeaGo/rands" + "runtime" + "strconv" + "testing" + "time" +) + +func TestNewCache(t *testing.T) { + cache := NewCache() + cache.Write("a", 1, time.Now().Unix()+3600) + cache.Write("b", 2, time.Now().Unix()+3601) + cache.Write("a", 1, time.Now().Unix()+3602) + cache.Write("d", 1, time.Now().Unix()+1) + + for _, piece := range cache.pieces { + if len(piece.m) > 0 { + for k, item := range piece.m { + t.Log(k, "=>", item.Value, item.expiredAt) + } + } + } + t.Log(cache.Read("a")) + time.Sleep(2 * time.Second) + t.Log(cache.Read("d")) +} + +func BenchmarkCache_Add(b *testing.B) { + runtime.GOMAXPROCS(1) + + cache := NewCache() + for i := 0; i < b.N; i++ { + cache.Write(strconv.Itoa(i), i, time.Now().Unix()+int64(i%1024)) + } +} + +func TestCache_IncreaseInt64(t *testing.T) { + var cache = NewCache() + + { + cache.IncreaseInt64("a", 1, time.Now().Unix()+3600) + t.Log(cache.Read("a")) + } + { + cache.IncreaseInt64("a", 1, time.Now().Unix()+3600+1) + t.Log(cache.Read("a")) + } + { + cache.Write("b", 1, time.Now().Unix()+3600+2) + t.Log(cache.Read("b")) + } + { + cache.IncreaseInt64("b", 1, time.Now().Unix()+3600+3) + t.Log(cache.Read("b")) + } +} + +func TestCache_Read(t *testing.T) { + runtime.GOMAXPROCS(1) + + var cache = NewCache(PiecesOption{Count: 32}) + + for i := 0; i < 10_000_000; i++ { + cache.Write("HELLO_WORLD_"+strconv.Itoa(i), i, time.Now().Unix()+int64(i%10240)+1) + } + + total := 0 + for _, piece := range cache.pieces { + //t.Log(len(piece.m), "keys") + total += len(piece.m) + } + t.Log(total, "total keys") + + before := time.Now() + for i := 0; i < 10_240; i++ { + _ = cache.Read("HELLO_WORLD_" + strconv.Itoa(i)) + } + t.Log(time.Since(before).Seconds()*1000, "ms") +} + +func TestCache_GC(t *testing.T) { + var cache = NewCache(&PiecesOption{Count: 5}) + cache.Write("a", 1, time.Now().Unix()+1) + cache.Write("b", 2, time.Now().Unix()+2) + cache.Write("c", 3, time.Now().Unix()+3) + cache.Write("d", 4, time.Now().Unix()+4) + cache.Write("e", 5, time.Now().Unix()+10) + + go func() { + for i := 0; i < 1000; i++ { + cache.Write("f", 1, time.Now().Unix()+1) + time.Sleep(10 * time.Millisecond) + } + }() + + for i := 0; i < 20; i++ { + cache.GC() + t.Log("items:", cache.Count()) + time.Sleep(1 * time.Second) + } + + t.Log("now:", time.Now().Unix()) + for _, p := range cache.pieces { + for k, v := range p.m { + t.Log(k, v.Value, v.expiredAt) + } + } +} + +func TestCache_GC2(t *testing.T) { + runtime.GOMAXPROCS(1) + + cache := NewCache() + for i := 0; i < 1_000_000; i++ { + cache.Write(strconv.Itoa(i), i, time.Now().Unix()+int64(rands.Int(0, 100))) + } + + for i := 0; i < 100; i++ { + t.Log(cache.Count(), "items") + time.Sleep(1 * time.Second) + } +} diff --git a/internal/ttlcache/item.go b/internal/ttlcache/item.go new file mode 100644 index 00000000..9720e295 --- /dev/null +++ b/internal/ttlcache/item.go @@ -0,0 +1,6 @@ +package ttlcache + +type Item struct { + Value interface{} + expiredAt int64 +} diff --git a/internal/ttlcache/option.go b/internal/ttlcache/option.go new file mode 100644 index 00000000..09a4cf89 --- /dev/null +++ b/internal/ttlcache/option.go @@ -0,0 +1,20 @@ +package ttlcache + +type OptionInterface interface { +} + +type PiecesOption struct { + Count int +} + +func NewPiecesOption(count int) *PiecesOption { + return &PiecesOption{Count: count} +} + +type MaxItemsOption struct { + Count int +} + +func NewMaxItemsOption(count int) *MaxItemsOption { + return &MaxItemsOption{Count: count} +} diff --git a/internal/ttlcache/piece.go b/internal/ttlcache/piece.go new file mode 100644 index 00000000..0f2834cb --- /dev/null +++ b/internal/ttlcache/piece.go @@ -0,0 +1,88 @@ +package ttlcache + +import ( + "github.com/iwind/TeaGo/types" + "sync" + "time" +) + +type Piece struct { + m map[uint64]*Item + maxItems int + locker sync.RWMutex +} + +func NewPiece(maxItems int) *Piece { + return &Piece{m: map[uint64]*Item{}, maxItems: maxItems} +} + +func (this *Piece) Add(key uint64, item *Item) () { + this.locker.Lock() + if len(this.m) >= this.maxItems { + this.locker.Unlock() + return + } + this.m[key] = item + this.locker.Unlock() +} + +func (this *Piece) IncreaseInt64(key uint64, delta int64, expiredAt int64) (result int64) { + this.locker.Lock() + item, ok := this.m[key] + if ok { + result := types.Int64(item.Value) + delta + item.Value = result + item.expiredAt = expiredAt + } else { + if len(this.m) < this.maxItems { + result = delta + this.m[key] = &Item{ + Value: delta, + expiredAt: expiredAt, + } + } + } + this.locker.Unlock() + return +} + +func (this *Piece) Delete(key uint64) { + this.locker.Lock() + delete(this.m, key) + this.locker.Unlock() +} + +func (this *Piece) Read(key uint64) (item *Item) { + this.locker.RLock() + item = this.m[key] + if item != nil && item.expiredAt < time.Now().Unix() { + item = nil + } + this.locker.RUnlock() + + return +} + +func (this *Piece) Count() (count int) { + this.locker.RLock() + count = len(this.m) + this.locker.RUnlock() + return +} + +func (this *Piece) GC() { + this.locker.Lock() + timestamp := time.Now().Unix() + for k, item := range this.m { + if item.expiredAt <= timestamp { + delete(this.m, k) + } + } + this.locker.Unlock() +} + +func (this *Piece) Destroy() { + this.locker.Lock() + this.m = nil + this.locker.Unlock() +} diff --git a/internal/ttlcache/piece_test.go b/internal/ttlcache/piece_test.go new file mode 100644 index 00000000..0ec0ff15 --- /dev/null +++ b/internal/ttlcache/piece_test.go @@ -0,0 +1,60 @@ +package ttlcache + +import ( + "github.com/iwind/TeaGo/rands" + "testing" + "time" +) + +func TestPiece_Add(t *testing.T) { + piece := NewPiece(10) + piece.Add(1, &Item{expiredAt: time.Now().Unix() + 3600}) + piece.Add(2, &Item{}) + piece.Add(3, &Item{}) + piece.Delete(3) + for key, item := range piece.m { + t.Log(key, item.Value) + } + t.Log(piece.Read(1)) +} + +func TestPiece_MaxItems(t *testing.T) { + piece := NewPiece(10) + for i := 0; i < 1000; i++ { + piece.Add(uint64(i), &Item{expiredAt: time.Now().Unix() + 3600}) + } + t.Log(len(piece.m)) +} + +func TestPiece_GC(t *testing.T) { + piece := NewPiece(10) + piece.Add(1, &Item{Value: 1, expiredAt: time.Now().Unix() + 1}) + piece.Add(2, &Item{Value: 2, expiredAt: time.Now().Unix() + 1}) + piece.Add(3, &Item{Value: 3, expiredAt: time.Now().Unix() + 1}) + t.Log("before gc ===") + for key, item := range piece.m { + t.Log(key, item.Value) + } + + time.Sleep(1 * time.Second) + piece.GC() + + t.Log("after gc ===") + for key, item := range piece.m { + t.Log(key, item.Value) + } +} + +func TestPiece_GC2(t *testing.T) { + piece := NewPiece(10) + for i := 0; i < 10_000; i++ { + piece.Add(uint64(i), &Item{Value: 1, expiredAt: time.Now().Unix() + int64(rands.Int(1, 10))}) + } + + time.Sleep(1 * time.Second) + + before := time.Now() + piece.GC() + t.Log(time.Since(before).Seconds()*1000, "ms") + t.Log(piece.Count()) +} diff --git a/internal/ttlcache/utils.go b/internal/ttlcache/utils.go new file mode 100644 index 00000000..59ff78bd --- /dev/null +++ b/internal/ttlcache/utils.go @@ -0,0 +1,7 @@ +package ttlcache + +import "github.com/cespare/xxhash" + +func HashKey(key []byte) uint64 { + return xxhash.Sum64(key) +} diff --git a/internal/ttlcache/utils_test.go b/internal/ttlcache/utils_test.go new file mode 100644 index 00000000..5f9c88ff --- /dev/null +++ b/internal/ttlcache/utils_test.go @@ -0,0 +1,13 @@ +package ttlcache + +import ( + "runtime" + "testing" +) + +func BenchmarkHashKey(b *testing.B) { + runtime.GOMAXPROCS(1) + for i := 0; i < b.N; i++ { + HashKey([]byte("HELLO,WORLDHELLO,WORLDHELLO,WORLDHELLO,WORLDHELLO,WORLDHELLO,WORLD")) + } +} diff --git a/internal/utils/ticker.go b/internal/utils/ticker.go new file mode 100644 index 00000000..e67e652a --- /dev/null +++ b/internal/utils/ticker.go @@ -0,0 +1,47 @@ +package utils + +import ( + "time" +) + +// 类似于time.Ticker,但能够真正地停止 +type Ticker struct { + raw *time.Ticker + + S chan bool + C <-chan time.Time + + isStopped bool +} + +// 创建新Ticker +func NewTicker(duration time.Duration) *Ticker { + raw := time.NewTicker(duration) + return &Ticker{ + raw: raw, + C: raw.C, + S: make(chan bool, 1), + } +} + +// 查找下一个Tick +func (this *Ticker) Next() bool { + select { + case <-this.raw.C: + return true + case <-this.S: + return false + } +} + +// 停止 +func (this *Ticker) Stop() { + if this.isStopped { + return + } + + this.isStopped = true + + this.raw.Stop() + this.S <- true +} diff --git a/internal/web/actions/default/servers/components/waf/export.go b/internal/web/actions/default/servers/components/waf/export.go index 443dca9b..08f4581c 100644 --- a/internal/web/actions/default/servers/components/waf/export.go +++ b/internal/web/actions/default/servers/components/waf/export.go @@ -1,6 +1,15 @@ package waf -import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" +import ( + "encoding/json" + "github.com/TeaOSLab/EdgeAdmin/internal/ttlcache" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeAdmin/internal/web/models" + "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" + "github.com/iwind/TeaGo/actions" + "github.com/iwind/TeaGo/rands" + "time" +) type ExportAction struct { actionutils.ParentAction @@ -10,6 +19,102 @@ func (this *ExportAction) Init() { this.Nav("", "", "export") } -func (this *ExportAction) RunGet(params struct{}) { +func (this *ExportAction) RunGet(params struct { + FirewallPolicyId int64 +}) { + policy, err := models.SharedHTTPFirewallPolicyDAO.FindEnabledPolicyConfig(this.AdminContext(), params.FirewallPolicyId) + if err != nil { + this.ErrorPage(err) + return + } + if policy == nil { + this.NotFound("firewallPolicy", policy.Id) + return + } + + inboundGroups := []*firewallconfigs.HTTPFirewallRuleGroup{} + outboundGroups := []*firewallconfigs.HTTPFirewallRuleGroup{} + if policy.Inbound != nil { + for _, g := range policy.Inbound.Groups { + if g.IsOn { + inboundGroups = append(inboundGroups, g) + } + } + } + if policy.Outbound != nil { + for _, g := range policy.Outbound.Groups { + if g.IsOn { + outboundGroups = append(outboundGroups, g) + } + } + } + this.Data["inboundGroups"] = inboundGroups + this.Data["outboundGroups"] = outboundGroups + this.Show() } + +func (this *ExportAction) RunPost(params struct { + FirewallPolicyId int64 + InboundGroupIds []int64 + OutboundGroupIds []int64 + + Must *actions.Must + CSRF *actionutils.CSRF +}) { + defer this.CreateLogInfo("导出WAF策略 %d", params.FirewallPolicyId) + + policy, err := models.SharedHTTPFirewallPolicyDAO.FindEnabledPolicyConfig(this.AdminContext(), params.FirewallPolicyId) + if err != nil { + this.ErrorPage(err) + return + } + if policy == nil { + this.NotFound("firewallPolicy", policy.Id) + return + } + + // inbound + newInboundGroups := []*firewallconfigs.HTTPFirewallRuleGroup{} + for _, inboundGroupId := range params.InboundGroupIds { + group := policy.FindRuleGroup(inboundGroupId) + if group != nil { + newInboundGroups = append(newInboundGroups, group) + } + } + if policy.Inbound == nil { + policy.Inbound = &firewallconfigs.HTTPFirewallInboundConfig{ + IsOn: true, + } + } + policy.Inbound.Groups = newInboundGroups + policy.Inbound.GroupRefs = nil + + // outbound + newOutboundGroups := []*firewallconfigs.HTTPFirewallRuleGroup{} + for _, outboundGroupId := range params.OutboundGroupIds { + group := policy.FindRuleGroup(outboundGroupId) + if group != nil { + newOutboundGroups = append(newOutboundGroups, group) + } + } + if policy.Outbound == nil { + policy.Outbound = &firewallconfigs.HTTPFirewallOutboundConfig{ + IsOn: true, + } + } + policy.Outbound.Groups = newOutboundGroups + policy.Outbound.GroupRefs = nil + + configJSON, err := json.Marshal(policy) + if err != nil { + this.ErrorPage(err) + return + } + + key := "waf." + rands.HexString(32) + ttlcache.DefaultCache.Write(key, configJSON, time.Now().Unix()+600) + + this.Data["key"] = key + this.Success() +} diff --git a/internal/web/actions/default/servers/components/waf/exportDownload.go b/internal/web/actions/default/servers/components/waf/exportDownload.go new file mode 100644 index 00000000..81f25b04 --- /dev/null +++ b/internal/web/actions/default/servers/components/waf/exportDownload.go @@ -0,0 +1,37 @@ +package waf + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/ttlcache" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "strconv" +) + +type ExportDownloadAction struct { + actionutils.ParentAction +} + +func (this *ExportDownloadAction) Init() { + this.Nav("", "", "") +} + +func (this *ExportDownloadAction) RunGet(params struct { + Key string +}) { + item := ttlcache.DefaultCache.Read(params.Key) + if item == nil || item.Value == nil { + this.WriteString("找不到要导出的内容") + return + } + + ttlcache.DefaultCache.Delete(params.Key) + + data, ok := item.Value.([]byte) + if ok { + this.AddHeader("Content-Disposition", "attachment; filename=\"WAF.json\";") + this.AddHeader("Content-Length", strconv.Itoa(len(data))) + this.Write(data) + } else { + this.WriteString("找不到要导出的内容") + return + } +} diff --git a/internal/web/actions/default/servers/components/waf/import.go b/internal/web/actions/default/servers/components/waf/import.go index 284519e5..33cbe5b5 100644 --- a/internal/web/actions/default/servers/components/waf/import.go +++ b/internal/web/actions/default/servers/components/waf/import.go @@ -1,6 +1,12 @@ package waf -import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" +import ( + "encoding/json" + "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" +) type ImportAction struct { actionutils.ParentAction @@ -13,3 +19,41 @@ func (this *ImportAction) Init() { func (this *ImportAction) RunGet(params struct{}) { this.Show() } + +func (this *ImportAction) RunPost(params struct { + FirewallPolicyId int64 + File *actions.File + + Must *actions.Must + CSRF *actionutils.CSRF +}) { + defer this.CreateLogInfo("从文件中导入规则到WAF策略 %d", params.FirewallPolicyId) + + if params.File == nil { + this.Fail("请上传要导入的文件") + } + if params.File.Ext != ".json" { + this.Fail("规则文件的扩展名只能是.json") + } + + data, err := params.File.Read() + if err != nil { + this.Fail("读取文件时发生错误:" + err.Error()) + } + + config := &firewallconfigs.HTTPFirewallPolicy{} + err = json.Unmarshal(data, config) + if err != nil { + this.Fail("解析文件时发生错误:" + err.Error()) + } + + _, err = this.RPC().HTTPFirewallPolicyRPC().ImportHTTPFirewallPolicy(this.AdminContext(), &pb.ImportHTTPFirewallPolicyRequest{ + FirewallPolicyId: params.FirewallPolicyId, + FirewallPolicyJSON: data, + }) + if err != nil { + this.Fail("导入失败:" + err.Error()) + } + + this.Success() +} diff --git a/internal/web/actions/default/servers/components/waf/init.go b/internal/web/actions/default/servers/components/waf/init.go index 08408109..0e0ed36b 100644 --- a/internal/web/actions/default/servers/components/waf/init.go +++ b/internal/web/actions/default/servers/components/waf/init.go @@ -24,6 +24,7 @@ func init() { GetPost("/update", new(UpdateAction)). GetPost("/test", new(TestAction)). GetPost("/export", new(ExportAction)). + Get("/exportDownload", new(ExportDownloadAction)). GetPost("/import", new(ImportAction)). Post("/updateGroupOn", new(UpdateGroupOnAction)). Post("/deleteGroup", new(DeleteGroupAction)). diff --git a/web/views/@default/servers/components/waf/@waf_menu.html b/web/views/@default/servers/components/waf/@waf_menu.html index 7fa45d2e..9aae73a9 100644 --- a/web/views/@default/servers/components/waf/@waf_menu.html +++ b/web/views/@default/servers/components/waf/@waf_menu.html @@ -7,8 +7,8 @@ IP管理 拦截日志 - 导入 - 导出--> + 导出 修改 \ No newline at end of file diff --git a/web/views/@default/servers/components/waf/export.css b/web/views/@default/servers/components/waf/export.css new file mode 100644 index 00000000..97d23c1e --- /dev/null +++ b/web/views/@default/servers/components/waf/export.css @@ -0,0 +1,7 @@ +.groups-box .group-box { + float: left; + width: 11em; + margin-top: 0.1em; + margin-bottom: 0.6em; +} +/*# sourceMappingURL=export.css.map */ \ No newline at end of file diff --git a/web/views/@default/servers/components/waf/export.css.map b/web/views/@default/servers/components/waf/export.css.map new file mode 100644 index 00000000..319fa3ab --- /dev/null +++ b/web/views/@default/servers/components/waf/export.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["export.less"],"names":[],"mappings":"AAAA,WACC;EACC,WAAA;EACA,WAAA;EACA,iBAAA;EACA,oBAAA","file":"export.css"} \ No newline at end of file diff --git a/web/views/@default/servers/components/waf/export.html b/web/views/@default/servers/components/waf/export.html index ffce8793..6c7951d9 100644 --- a/web/views/@default/servers/components/waf/export.html +++ b/web/views/@default/servers/components/waf/export.html @@ -1,5 +1,33 @@ {$layout} +{$template "waf_menu"} - {$template "waf_menu"} +
+ + -

此功能暂未开放,敬请期待。

\ No newline at end of file + + + + + + + + + +
选择入站规则 + 暂时还没有入站规则。 +
+
+ {{g.name}} +
+
+
选择出站规则 + 暂时还没有出站规则。 +
+
+ {{g.name}} +
+
+
+ 导出 +
\ No newline at end of file diff --git a/web/views/@default/servers/components/waf/export.js b/web/views/@default/servers/components/waf/export.js new file mode 100644 index 00000000..e27b6bcc --- /dev/null +++ b/web/views/@default/servers/components/waf/export.js @@ -0,0 +1,5 @@ +Tea.context(function () { + this.success = function (resp) { + window.location = "/servers/components/waf/exportDownload?key=" + resp.data.key + } +}) \ No newline at end of file diff --git a/web/views/@default/servers/components/waf/export.less b/web/views/@default/servers/components/waf/export.less new file mode 100644 index 00000000..a94d9247 --- /dev/null +++ b/web/views/@default/servers/components/waf/export.less @@ -0,0 +1,8 @@ +.groups-box { + .group-box { + float: left; + width: 11em; + margin-top: 0.1em; + margin-bottom: 0.6em; + } +} \ No newline at end of file diff --git a/web/views/@default/servers/components/waf/import.html b/web/views/@default/servers/components/waf/import.html index ffce8793..62e14319 100644 --- a/web/views/@default/servers/components/waf/import.html +++ b/web/views/@default/servers/components/waf/import.html @@ -1,5 +1,16 @@ {$layout} +{$template "waf_menu"} - {$template "waf_menu"} - -

此功能暂未开放,敬请期待。

\ No newline at end of file +
+ + + + + + + +
选择要倒入的规则文件 + +
+ 导入 +
\ No newline at end of file diff --git a/web/views/@default/servers/components/waf/import.js b/web/views/@default/servers/components/waf/import.js new file mode 100644 index 00000000..83719ef2 --- /dev/null +++ b/web/views/@default/servers/components/waf/import.js @@ -0,0 +1,3 @@ +Tea.context(function () { + this.success = NotifyReloadSuccess("导入成功") +}) \ No newline at end of file