diff --git a/internal/rpc/rpc_client.go b/internal/rpc/rpc_client.go index 5482c58f..eb7ef065 100644 --- a/internal/rpc/rpc_client.go +++ b/internal/rpc/rpc_client.go @@ -75,6 +75,10 @@ func (this *RPCClient) NodeRegionRPC() pb.NodeRegionServiceClient { return pb.NewNodeRegionServiceClient(this.pickConn()) } +func (this *RPCClient) NodePriceItemRPC() pb.NodePriceItemServiceClient { + return pb.NewNodePriceItemServiceClient(this.pickConn()) +} + func (this *RPCClient) NodeIPAddressRPC() pb.NodeIPAddressServiceClient { return pb.NewNodeIPAddressServiceClient(this.pickConn()) } diff --git a/internal/web/actions/default/clusters/regions/index.go b/internal/web/actions/default/clusters/regions/index.go index 764d1fca..bb9fe74b 100644 --- a/internal/web/actions/default/clusters/regions/index.go +++ b/internal/web/actions/default/clusters/regions/index.go @@ -11,7 +11,7 @@ type IndexAction struct { } func (this *IndexAction) Init() { - this.Nav("", "", "") + this.Nav("", "", "region") } func (this *IndexAction) RunGet(params struct{}) { diff --git a/internal/web/actions/default/clusters/regions/init.go b/internal/web/actions/default/clusters/regions/init.go index 3408aad4..24c8bf6a 100644 --- a/internal/web/actions/default/clusters/regions/init.go +++ b/internal/web/actions/default/clusters/regions/init.go @@ -19,6 +19,8 @@ func init() { Post("/delete", new(DeleteAction)). Post("/sort", new(SortAction)). GetPost("/selectPopup", new(SelectPopupAction)). + GetPost("/prices", new(PricesAction)). + GetPost("/updatePricePopup", new(UpdatePricePopupAction)). EndAll() }) } diff --git a/internal/web/actions/default/clusters/regions/items/createPopup.go b/internal/web/actions/default/clusters/regions/items/createPopup.go new file mode 100644 index 00000000..badaf18e --- /dev/null +++ b/internal/web/actions/default/clusters/regions/items/createPopup.go @@ -0,0 +1,50 @@ +package items + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/regions/regionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/actions" +) + +type CreatePopupAction struct { + actionutils.ParentAction +} + +func (this *CreatePopupAction) Init() { + this.Nav("", "", "") +} + +func (this *CreatePopupAction) RunGet(params struct{}) { + this.Show() +} + +func (this *CreatePopupAction) RunPost(params struct { + Name string + BitsFrom int64 + BitsTo int64 + + Must *actions.Must + CSRF *actionutils.CSRF +}) { + params.Must. + Field("name", params.Name). + Require("请输入名称"). + Field("bitsFrom", params.BitsFrom). + Gte(0, "请输入不小于0的整数"). + Field("bitsTo", params.BitsTo). + Gte(0, "请输入不小于0的整数") + + createResp, err := this.RPC().NodePriceItemRPC().CreateNodePriceItem(this.AdminContext(), &pb.CreateNodePriceItemRequest{ + Name: params.Name, + Type: regionutils.PriceTypeTraffic, + BitsFrom: params.BitsFrom * 1000 * 1000, + BitsTo: params.BitsTo * 1000 * 1000, + }) + if err != nil { + this.ErrorPage(err) + return + } + defer this.CreateLogInfo("创建流量价格项目", createResp.NodePriceItemId) + this.Success() +} diff --git a/internal/web/actions/default/clusters/regions/items/delete.go b/internal/web/actions/default/clusters/regions/items/delete.go new file mode 100644 index 00000000..81fd965a --- /dev/null +++ b/internal/web/actions/default/clusters/regions/items/delete.go @@ -0,0 +1,24 @@ +package items + +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("删除流量价格项目 %d", params.ItemId) + + _, err := this.RPC().NodePriceItemRPC().DeleteNodePriceItem(this.AdminContext(), &pb.DeleteNodePriceItemRequest{NodePriceItemId: params.ItemId}) + if err != nil { + this.ErrorPage(err) + return + } + + this.Success() +} diff --git a/internal/web/actions/default/clusters/regions/items/init.go b/internal/web/actions/default/clusters/regions/items/init.go new file mode 100644 index 00000000..a18c92e6 --- /dev/null +++ b/internal/web/actions/default/clusters/regions/items/init.go @@ -0,0 +1,21 @@ +package items + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/configloaders" + "github.com/TeaOSLab/EdgeAdmin/internal/web/helpers" + "github.com/iwind/TeaGo" +) + +func init() { + TeaGo.BeforeStart(func(server *TeaGo.Server) { + server. + Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNode)). + Data("teaMenu", "clusters"). + Data("teaSubMenu", "region"). + Prefix("/clusters/regions/items"). + GetPost("/createPopup", new(CreatePopupAction)). + GetPost("/updatePopup", new(UpdatePopupAction)). + Post("/delete", new(DeleteAction)). + EndAll() + }) +} diff --git a/internal/web/actions/default/clusters/regions/items/updatePopup.go b/internal/web/actions/default/clusters/regions/items/updatePopup.go new file mode 100644 index 00000000..515f99a1 --- /dev/null +++ b/internal/web/actions/default/clusters/regions/items/updatePopup.go @@ -0,0 +1,73 @@ +package items + +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 UpdatePopupAction struct { + actionutils.ParentAction +} + +func (this *UpdatePopupAction) Init() { + this.Nav("", "", "") +} + +func (this *UpdatePopupAction) RunGet(params struct { + ItemId int64 +}) { + itemResp, err := this.RPC().NodePriceItemRPC().FindEnabledNodePriceItem(this.AdminContext(), &pb.FindEnabledNodePriceItemRequest{NodePriceItemId: params.ItemId}) + if err != nil { + this.ErrorPage(err) + return + } + item := itemResp.NodePriceItem + if item == nil { + this.NotFound("nodePriceItem", params.ItemId) + return + } + + this.Data["item"] = maps.Map{ + "id": item.Id, + "name": item.Name, + "bitsFrom": item.BitsFrom, + "bitsTo": item.BitsTo, + } + + this.Show() +} + +func (this *UpdatePopupAction) RunPost(params struct { + ItemId int64 + Name string + BitsFrom int64 + BitsTo int64 + + Must *actions.Must + CSRF *actionutils.CSRF +}) { + defer this.CreateLogInfo("修改流量价格项目", params.ItemId) + + params.Must. + Field("name", params.Name). + Require("请输入名称"). + Field("bitsFrom", params.BitsFrom). + Gte(0, "请输入不小于0的整数"). + Field("bitsTo", params.BitsTo). + Gte(0, "请输入不小于0的整数") + + _, err := this.RPC().NodePriceItemRPC().UpdateNodePriceItem(this.AdminContext(), &pb.UpdateNodePriceItemRequest{ + NodePriceItemId: params.ItemId, + Name: params.Name, + BitsFrom: params.BitsFrom * 1000 * 1000, + BitsTo: params.BitsTo * 1000 * 1000, + }) + if err != nil { + this.ErrorPage(err) + return + } + + this.Success() +} diff --git a/internal/web/actions/default/clusters/regions/prices.go b/internal/web/actions/default/clusters/regions/prices.go new file mode 100644 index 00000000..8235d2cd --- /dev/null +++ b/internal/web/actions/default/clusters/regions/prices.go @@ -0,0 +1,84 @@ +package regions + +import ( + "encoding/json" + "fmt" + "github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/regions/regionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/maps" +) + +type PricesAction struct { + actionutils.ParentAction +} + +func (this *PricesAction) Init() { + this.Nav("", "", "price") +} + +func (this *PricesAction) RunGet(params struct{}) { + // 所有价格项目 + itemsResp, err := this.RPC().NodePriceItemRPC().FindAllEnabledAndOnNodePriceItems(this.AdminContext(), &pb.FindAllEnabledAndOnNodePriceItemsRequest{Type: regionutils.PriceTypeTraffic}) + if err != nil { + this.ErrorPage(err) + return + } + itemMaps := []maps.Map{} + for _, item := range itemsResp.NodePriceItems { + + itemMaps = append(itemMaps, maps.Map{ + "id": item.Id, + "name": item.Name, + "bitsFromString": this.formatBits(item.BitsFrom), + "bitsToString": this.formatBits(item.BitsTo), + }) + } + this.Data["items"] = itemMaps + + // 所有区域 + regionsResp, err := this.RPC().NodeRegionRPC().FindAllEnabledNodeRegions(this.AdminContext(), &pb.FindAllEnabledNodeRegionsRequest{}) + if err != nil { + this.ErrorPage(err) + return + } + regionMaps := []maps.Map{} + for _, region := range regionsResp.NodeRegions { + pricesMap := map[string]float32{} + if len(region.PricesJSON) > 0 { + err = json.Unmarshal(region.PricesJSON, &pricesMap) + if err != nil { + this.ErrorPage(err) + return + } + } + regionMaps = append(regionMaps, maps.Map{ + "id": region.Id, + "isOn": region.IsOn, + "name": region.Name, + "prices": pricesMap, + }) + } + this.Data["regions"] = regionMaps + + this.Show() +} + +func (this *PricesAction) formatBits(bits int64) string { + sizeHuman := "" + if bits < 1000 { + sizeHuman = numberutils.FormatInt64(bits) + "BPS" + } else if bits < 1_000_000 { + sizeHuman = fmt.Sprintf("%.2fKBPS", float64(bits)/1000) + } else if bits < 1_000_000_000 { + sizeHuman = fmt.Sprintf("%.2fMBPS", float64(bits)/1000/1000) + } else if bits < 1_000_000_000_000 { + sizeHuman = fmt.Sprintf("%.2fGBPS", float64(bits)/1000/1000/1000) + } else if bits < 1_000_000_000_000_000 { + sizeHuman = fmt.Sprintf("%.2fTBPS", float64(bits)/1000/1000/1000/1000) + } else { + sizeHuman = fmt.Sprintf("%.2fPTBPS", float64(bits)/1000/1000/1000/1000/1000) + } + return sizeHuman +} diff --git a/internal/web/actions/default/clusters/regions/regionutils/consts.go b/internal/web/actions/default/clusters/regions/regionutils/consts.go new file mode 100644 index 00000000..a7841773 --- /dev/null +++ b/internal/web/actions/default/clusters/regions/regionutils/consts.go @@ -0,0 +1,5 @@ +package regionutils + +const ( + PriceTypeTraffic = "traffic" +) diff --git a/internal/web/actions/default/clusters/regions/updatePricePopup.go b/internal/web/actions/default/clusters/regions/updatePricePopup.go new file mode 100644 index 00000000..68549cea --- /dev/null +++ b/internal/web/actions/default/clusters/regions/updatePricePopup.go @@ -0,0 +1,94 @@ +package regions + +import ( + "encoding/json" + "github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils" + "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 UpdatePricePopupAction struct { + actionutils.ParentAction +} + +func (this *UpdatePricePopupAction) Init() { + this.Nav("", "", "") +} + +func (this *UpdatePricePopupAction) RunGet(params struct { + RegionId int64 + ItemId int64 +}) { + // 区域 + regionResp, err := this.RPC().NodeRegionRPC().FindEnabledNodeRegion(this.AdminContext(), &pb.FindEnabledNodeRegionRequest{NodeRegionId: params.RegionId}) + if err != nil { + this.ErrorPage(err) + return + } + region := regionResp.NodeRegion + if region == nil { + this.NotFound("nodeRegion", params.RegionId) + return + } + this.Data["region"] = maps.Map{ + "id": region.Id, + "isOn": region.IsOn, + "name": region.Name, + } + + // 当前价格 + pricesMap := map[string]float32{} + if len(region.PricesJSON) > 0 { + err = json.Unmarshal(region.PricesJSON, &pricesMap) + if err != nil { + this.ErrorPage(err) + return + } + } + this.Data["price"] = pricesMap[numberutils.FormatInt64(params.ItemId)] + + // 价格项 + itemResp, err := this.RPC().NodePriceItemRPC().FindEnabledNodePriceItem(this.AdminContext(), &pb.FindEnabledNodePriceItemRequest{NodePriceItemId: params.ItemId}) + if err != nil { + this.ErrorPage(err) + return + } + item := itemResp.NodePriceItem + if item == nil { + this.NotFound("nodePriceItem", params.ItemId) + return + } + + this.Data["item"] = maps.Map{ + "id": item.Id, + "name": item.Name, + "bitsFrom": item.BitsFrom, + "bitsTo": item.BitsTo, + } + + this.Show() +} + +func (this *UpdatePricePopupAction) RunPost(params struct { + RegionId int64 + ItemId int64 + Price float32 + + Must *actions.Must + CSRF *actionutils.CSRF +}) { + defer this.CreateLogInfo("修改区域 %d-价格项 %d 的价格", params.RegionId, params.ItemId) + + _, err := this.RPC().NodeRegionRPC().UpdateNodeRegionPrice(this.AdminContext(), &pb.UpdateNodeRegionPriceRequest{ + NodeRegionId: params.RegionId, + NodeItemId: params.ItemId, + Price: params.Price, + }) + if err != nil { + this.ErrorPage(err) + return + } + this.Success() +} diff --git a/internal/web/import.go b/internal/web/import.go index 8be05c47..46d96209 100644 --- a/internal/web/import.go +++ b/internal/web/import.go @@ -11,6 +11,7 @@ import ( _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/regions" + _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/regions/items" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/csrf" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dashboard" _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/db" diff --git a/web/views/@default/clusters/regions/@menu.html b/web/views/@default/clusters/regions/@menu.html new file mode 100644 index 00000000..199ce0d4 --- /dev/null +++ b/web/views/@default/clusters/regions/@menu.html @@ -0,0 +1,6 @@ + + 区域 + 价格 + | + + \ No newline at end of file diff --git a/web/views/@default/clusters/regions/index.html b/web/views/@default/clusters/regions/index.html index 3cb7efb9..7280626f 100644 --- a/web/views/@default/clusters/regions/index.html +++ b/web/views/@default/clusters/regions/index.html @@ -1,14 +1,13 @@ {$layout} +{$template "menu"} - + 创建 - | - - +

暂时还没有区域。

- +
diff --git a/web/views/@default/clusters/regions/items/createPopup.html b/web/views/@default/clusters/regions/items/createPopup.html new file mode 100644 index 00000000..9ec3aeaa --- /dev/null +++ b/web/views/@default/clusters/regions/items/createPopup.html @@ -0,0 +1,36 @@ +{$layout "layout_popup"} + +

添加价格项

+ + + +
+ + + + + + + + + + + + +
名称 * + +
最低流量 * +
+ + MBPS +
+

采用1000进制,相当于{{bitsFromMB}}

+
最高流量 * +
+ + MBPS +
+

采用1000进制,相当于{{bitsToMB}}

+
+ + \ No newline at end of file diff --git a/web/views/@default/clusters/regions/items/createPopup.js b/web/views/@default/clusters/regions/items/createPopup.js new file mode 100644 index 00000000..1b7c285d --- /dev/null +++ b/web/views/@default/clusters/regions/items/createPopup.js @@ -0,0 +1,42 @@ +Tea.context(function () { + this.bitsFrom = 0 + this.bitsFromMB = "" + + this.bitsTo = 0 + this.bitsToMB = "" + + this.$delay(function () { + let that = this + this.$watch("bitsFrom", function (v) { + this.bitsFromMB = that.formatBits(v) + }) + this.$watch("bitsTo", function (v) { + this.bitsToMB = that.formatBits(v) + }) + }) + + this.formatBits = function (bits) { + bits = parseInt(bits) + if (isNaN(bits)) { + bits = 0 + } + + if (bits < 1000) { + return bits + "MB" + } + + if (bits < 1000 * 1000) { + return (bits / 1000) + "GB" + } + + if (bits < 1000 * 1000 * 1000) { + return (bits / 1000 / 1000) + "TB" + } + + if (bits < 1000 * 1000 * 1000 * 1000) { + return (bits / 1000 / 1000 / 1000) + "PB" + } + + return "" + } +}) \ No newline at end of file diff --git a/web/views/@default/clusters/regions/items/updatePopup.html b/web/views/@default/clusters/regions/items/updatePopup.html new file mode 100644 index 00000000..a0587a0e --- /dev/null +++ b/web/views/@default/clusters/regions/items/updatePopup.html @@ -0,0 +1,37 @@ +{$layout "layout_popup"} + +

修改价格项

+ +
+ + + + + + + + + + + + + + + +
名称 * + +
最低流量 * +
+ + MBPS +
+

采用1000进制,相当于{{bitsFromMB}}

+
最高流量 * +
+ + MBPS +
+

采用1000进制,相当于{{bitsToMB}}

+
+ +
\ No newline at end of file diff --git a/web/views/@default/clusters/regions/items/updatePopup.js b/web/views/@default/clusters/regions/items/updatePopup.js new file mode 100644 index 00000000..93454062 --- /dev/null +++ b/web/views/@default/clusters/regions/items/updatePopup.js @@ -0,0 +1,45 @@ +Tea.context(function () { + this.bitsFrom = this.item.bitsFrom / 1000 / 1000 + this.bitsFromMB = "" + + this.bitsTo = this.item.bitsTo / 1000 / 1000 + this.bitsToMB = "" + + this.$delay(function () { + let that = this + this.$watch("bitsFrom", function (v) { + this.bitsFromMB = that.formatBits(v) + }) + this.$watch("bitsTo", function (v) { + this.bitsToMB = that.formatBits(v) + }) + + this.bitsFromMB = that.formatBits(this.bitsFrom) + this.bitsToMB = that.formatBits(this.bitsTo) + }) + + this.formatBits = function (bits) { + bits = parseInt(bits) + if (isNaN(bits)) { + bits = 0 + } + + if (bits < 1000) { + return bits + "MB" + } + + if (bits < 1000 * 1000) { + return (bits / 1000) + "GB" + } + + if (bits < 1000 * 1000 * 1000) { + return (bits / 1000 / 1000) + "TB" + } + + if (bits < 1000 * 1000 * 1000 * 1000) { + return (bits / 1000 / 1000 / 1000) + "PB" + } + + return "" + } +}) \ No newline at end of file diff --git a/web/views/@default/clusters/regions/prices.css b/web/views/@default/clusters/regions/prices.css new file mode 100644 index 00000000..76c19ae1 --- /dev/null +++ b/web/views/@default/clusters/regions/prices.css @@ -0,0 +1,15 @@ +th span { + font-size: 0.9em; + font-weight: normal; + color: grey; +} +th a { + font-weight: normal; +} +td a { + display: none; +} +td:hover a { + display: inline; +} +/*# sourceMappingURL=prices.css.map */ \ No newline at end of file diff --git a/web/views/@default/clusters/regions/prices.css.map b/web/views/@default/clusters/regions/prices.css.map new file mode 100644 index 00000000..42e84196 --- /dev/null +++ b/web/views/@default/clusters/regions/prices.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["prices.less"],"names":[],"mappings":"AAAA,EACC;EACC,gBAAA;EACA,mBAAA;EACA,WAAA;;AAJF,EAOC;EACC,mBAAA;;AAIF,EACC;EACC,aAAA;;AAIF,EAAE,MACD;EACC,eAAA","file":"prices.css"} \ No newline at end of file diff --git a/web/views/@default/clusters/regions/prices.html b/web/views/@default/clusters/regions/prices.html new file mode 100644 index 00000000..1a9acd71 --- /dev/null +++ b/web/views/@default/clusters/regions/prices.html @@ -0,0 +1,31 @@ +{$layout} +{$template "menu"} + +

暂时还没有区域。

+ + + + + + + + + + + + + + +
区域\范围 + {{item.name}} +     + +
+ {{item.bitsFromString}}-{{item.bitsToString}} +
+ [+添加价格项] +
{{region.name}} + ¥{{region.prices[item.id.toString()]}}元/GB   + + [设置] +
\ No newline at end of file diff --git a/web/views/@default/clusters/regions/prices.js b/web/views/@default/clusters/regions/prices.js new file mode 100644 index 00000000..2777f7e4 --- /dev/null +++ b/web/views/@default/clusters/regions/prices.js @@ -0,0 +1,42 @@ +Tea.context(function () { + this.createItem = function () { + teaweb.popup(Tea.url(".items.createPopup"), { + callback: function () { + teaweb.success("保存成功", function () { + teaweb.reload() + }) + } + }) + } + + this.updateItem = function (itemId) { + teaweb.popup(Tea.url(".items.updatePopup", {itemId: itemId}), { + callback: function () { + teaweb.success("保存成功", function () { + teaweb.reload() + }) + } + }) + } + + this.deleteItem = function (itemId) { + let that = this + teaweb.confirm("确定要删除此价格项吗?", function () { + that.$post(".items.delete") + .params({ + itemId: itemId + }) + .refresh() + }) + } + + this.updatePrice = function (regionId, itemId) { + teaweb.popup(Tea.url(".updatePricePopup", {regionId: regionId, itemId: itemId}), { + callback: function () { + teaweb.success("保存成功", function () { + teaweb.reload() + }) + } + }) + } +}) \ No newline at end of file diff --git a/web/views/@default/clusters/regions/prices.less b/web/views/@default/clusters/regions/prices.less new file mode 100644 index 00000000..44278d2f --- /dev/null +++ b/web/views/@default/clusters/regions/prices.less @@ -0,0 +1,23 @@ +th { + span { + font-size: 0.9em; + font-weight: normal; + color: grey; + } + + a { + font-weight: normal; + } +} + +td { + a { + display: none; + } +} + +td:hover { + a { + display: inline; + } +} \ No newline at end of file diff --git a/web/views/@default/clusters/regions/updatePricePopup.html b/web/views/@default/clusters/regions/updatePricePopup.html new file mode 100644 index 00000000..1dd56495 --- /dev/null +++ b/web/views/@default/clusters/regions/updatePricePopup.html @@ -0,0 +1,30 @@ +{$layout "layout_popup"} + +

修改价格

+
+ + + + + + + + + + + + + + + + + +
区域{{region.name}}
价格项{{item.name}}
单位价格 * +
+ + 元/GB +
+
+ + +
\ No newline at end of file