Initial commit (code only without large binaries)

This commit is contained in:
robin
2026-02-15 18:58:44 +08:00
commit 35df75498f
9442 changed files with 1495866 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accountutils
import (
"errors"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
)
// InitAccount 初始化账户
func InitAccount(parent *actionutils.ParentAction, accountId int64) (*pb.UserAccount, error) {
resp, err := parent.RPC().UserAccountRPC().FindEnabledUserAccount(parent.AdminContext(), &pb.FindEnabledUserAccountRequest{UserAccountId: accountId})
if err != nil {
return nil, err
}
var account = resp.UserAccount
if account == nil {
return nil, errors.New("can not find account with id '" + types.String(accountId) + "'")
}
var userMap = maps.Map{"fullname": "", "username": ""}
var user = account.User
if user != nil {
userMap = maps.Map{"fullname": user.Fullname, "username": user.Username}
}
parent.Data["account"] = maps.Map{
"id": account.Id,
"user": userMap,
}
return account, nil
}

View File

@@ -0,0 +1,65 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package account
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/accounts/account/accountutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "index")
}
func (this *IndexAction) RunGet(params struct {
AccountId int64
Keyword string
}) {
this.Data["keyword"] = ""
account, err := accountutils.InitAccount(this.Parent(), params.AccountId)
if err != nil {
this.ErrorPage(err)
return
}
var accountMap = this.Data.GetMap("account")
accountMap["total"] = account.Total
accountMap["totalFrozen"] = account.TotalFrozen
this.Data["account"] = accountMap
// 最近操作记录
logsResp, err := this.RPC().UserAccountLogRPC().ListUserAccountLogs(this.AdminContext(), &pb.ListUserAccountLogsRequest{
UserAccountId: account.Id,
Offset: 0,
Size: 10,
})
if err != nil {
this.ErrorPage(err)
return
}
var logMaps = []maps.Map{}
for _, log := range logsResp.UserAccountLogs {
logMaps = append(logMaps, maps.Map{
"id": log.Id,
"description": log.Description,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", log.CreatedAt),
"delta": log.Delta,
"deltaFrozen": log.DeltaFrozen,
"total": log.Total,
"totalFrozen": log.TotalFrozen,
"event": userconfigs.FindAccountEvent(log.EventType),
})
}
this.Data["logs"] = logMaps
this.Show()
}

View File

@@ -0,0 +1,68 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package account
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/accounts/account/accountutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type LogsAction struct {
actionutils.ParentAction
}
func (this *LogsAction) Init() {
this.Nav("", "", "log")
}
func (this *LogsAction) RunGet(params struct {
AccountId int64
Keyword string
}) {
this.Data["keyword"] = params.Keyword
_, err := accountutils.InitAccount(this.Parent(), params.AccountId)
if err != nil {
this.ErrorPage(err)
return
}
countResp, err := this.RPC().UserAccountLogRPC().CountUserAccountLogs(this.AdminContext(), &pb.CountUserAccountLogsRequest{UserAccountId: params.AccountId})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
var page = this.NewPage(count)
this.Data["page"] = page.AsHTML()
logsResp, err := this.RPC().UserAccountLogRPC().ListUserAccountLogs(this.AdminContext(), &pb.ListUserAccountLogsRequest{
UserAccountId: params.AccountId,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var logMaps = []maps.Map{}
for _, log := range logsResp.UserAccountLogs {
logMaps = append(logMaps, maps.Map{
"id": log.Id,
"description": log.Description,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", log.CreatedAt),
"delta": log.Delta,
"deltaFrozen": log.DeltaFrozen,
"total": log.Total,
"totalFrozen": log.TotalFrozen,
"event": userconfigs.FindAccountEvent(log.EventType),
})
}
this.Data["logs"] = logMaps
this.Show()
}

View File

@@ -0,0 +1,98 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package account
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/accounts/account/accountutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/types"
)
type UpdateAction struct {
actionutils.ParentAction
}
func (this *UpdateAction) Init() {
this.Nav("", "", "update")
}
func (this *UpdateAction) RunGet(params struct {
AccountId int64
}) {
account, err := accountutils.InitAccount(this.Parent(), params.AccountId)
if err != nil {
this.ErrorPage(err)
return
}
var accountMap = this.Data.GetMap("account")
accountMap["total"] = account.Total
accountMap["totalFrozen"] = account.TotalFrozen
this.Data["account"] = accountMap
this.Data["eventTypes"] = userconfigs.FindAllAccountEventTypes()
this.Show()
}
func (this *UpdateAction) RunPost(params struct {
AccountId int64
EventType string
Delta float64
Description string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.UserAccount_LogUpdateUserAccount, params.AccountId, params.Description)
if params.AccountId <= 0 {
this.Fail("请选择要操作的账户")
}
var event = userconfigs.FindAccountEvent(params.EventType)
if event == nil {
this.Fail("请选择操作类型")
}
if params.Delta < 0 {
this.Fail("操作金额不能小于0")
}
var delta = params.Delta
if !event.IsPositive {
delta = -delta
}
accountResp, err := this.RPC().UserAccountRPC().FindEnabledUserAccount(this.AdminContext(), &pb.FindEnabledUserAccountRequest{UserAccountId: params.AccountId})
if err != nil {
this.ErrorPage(err)
return
}
var account = accountResp.UserAccount
if account == nil {
this.Fail("找不到用户的账户")
}
// 检查余额是否足够
if !event.IsPositive && delta+account.Total < 0 {
this.Fail("账户余额不足,最多只能操作" + types.String(account.Total) + "元")
}
_, err = this.RPC().UserAccountRPC().UpdateUserAccount(this.AdminContext(), &pb.UpdateUserAccountRequest{
UserAccountId: account.Id,
Delta: delta,
EventType: params.EventType,
Description: params.Description,
ParamsJSON: nil,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,62 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accounts
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "index")
}
func (this *IndexAction) RunGet(params struct {
Keyword string
}) {
this.Data["keyword"] = params.Keyword
countResp, err := this.RPC().UserAccountRPC().CountUserAccounts(this.AdminContext(), &pb.CountUserAccountsRequest{Keyword: params.Keyword})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
var page = this.NewPage(count)
this.Data["page"] = page.AsHTML()
accountsResp, err := this.RPC().UserAccountRPC().ListUserAccounts(this.AdminContext(), &pb.ListUserAccountsRequest{
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var accountMaps = []maps.Map{}
for _, account := range accountsResp.UserAccounts {
if account.User == nil {
account.User = &pb.User{}
}
accountMaps = append(accountMaps, maps.Map{
"id": account.Id,
"total": account.Total,
"user": maps.Map{
"id": account.UserId,
"username": account.User.Username,
"fullname": account.User.Fullname,
},
})
}
this.Data["accounts"] = accountMaps
this.Show()
}

View File

@@ -0,0 +1,36 @@
//go:build plus
package accounts
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/accounts/account"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(plus.NewHelper(plus.ComponentCodeFinance)).
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeFinance)).
Data("teaMenu", "finance").
Data("teaSubMenu", "accounts").
// 财务管理
Prefix("/finance/accounts").
Get("", new(IndexAction)).
GetPost("/update", new(UpdateAction)).
Post("/userAccount", new(UserAccountAction)).
// 账户
Prefix("/finance/accounts/account").
Get("", new(account.IndexAction)).
Get("/logs", new(account.LogsAction)).
GetPost("/update", new(account.UpdateAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,84 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accounts
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/types"
)
type UpdateAction struct {
actionutils.ParentAction
}
func (this *UpdateAction) Init() {
this.Nav("", "", "update")
}
func (this *UpdateAction) RunGet(params struct{}) {
this.Data["eventTypes"] = userconfigs.FindAllAccountEventTypes()
this.Show()
}
func (this *UpdateAction) RunPost(params struct {
UserId int64
EventType string
Delta float64
Description string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.UserAccount_LogUpdateUserAccount, params.UserId, params.Description)
if params.UserId <= 0 {
this.Fail("请选择要操作的用户")
}
var event = userconfigs.FindAccountEvent(params.EventType)
if event == nil {
this.Fail("请选择操作类型")
}
if params.Delta < 0 {
this.Fail("操作金额不能小于0")
}
var delta = params.Delta
if !event.IsPositive {
delta = -delta
}
accountResp, err := this.RPC().UserAccountRPC().FindEnabledUserAccountWithUserId(this.AdminContext(), &pb.FindEnabledUserAccountWithUserIdRequest{UserId: params.UserId})
if err != nil {
this.ErrorPage(err)
return
}
var account = accountResp.UserAccount
if account == nil {
this.Fail("找不到用户的账户")
}
// 检查余额是否足够
if !event.IsPositive && delta+account.Total < 0 {
this.Fail("账户余额不足,最多只能操作" + types.String(account.Total) + "元")
}
_, err = this.RPC().UserAccountRPC().UpdateUserAccount(this.AdminContext(), &pb.UpdateUserAccountRequest{
UserAccountId: account.Id,
Delta: delta,
EventType: params.EventType,
Description: params.Description,
ParamsJSON: nil,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,34 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accounts
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type UserAccountAction struct {
actionutils.ParentAction
}
func (this *UserAccountAction) RunPost(params struct {
UserId int64
}) {
resp, err := this.RPC().UserAccountRPC().FindEnabledUserAccountWithUserId(this.AdminContext(), &pb.FindEnabledUserAccountWithUserIdRequest{UserId: params.UserId})
if err != nil {
this.ErrorPage(err)
return
}
var account = resp.UserAccount
if account == nil {
account = &pb.UserAccount{}
}
this.Data["account"] = maps.Map{
"total": account.Total,
"totalFrozen": account.TotalFrozen,
}
this.Success()
}

View File

@@ -0,0 +1,173 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package bills
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type BillAction struct {
actionutils.ParentAction
}
func (this *BillAction) Init() {
this.Nav("", "", "index")
}
func (this *BillAction) RunGet(params struct {
BillId int64
}) {
userBillResp, err := this.RPC().UserBillRPC().FindUserBill(this.AdminContext(), &pb.FindUserBillRequest{UserBillId: params.BillId})
if err != nil {
this.ErrorPage(err)
return
}
var bill = userBillResp.UserBill
if bill == nil {
this.NotFound("user bill", params.BillId)
return
}
var userMap maps.Map = nil
if bill.User != nil {
userMap = maps.Map{
"id": bill.User.Id,
"fullname": bill.User.Fullname,
"username": bill.User.Username,
}
}
var month = bill.Month
var dayFrom = bill.DayFrom
var dayTo = bill.DayTo
if len(month) == 6 {
month = month[:4] + "-" + month[4:]
}
if len(dayFrom) == 8 {
dayFrom = dayFrom[:4] + "-" + dayFrom[4:6] + "-" + dayFrom[6:]
}
if len(dayTo) == 8 {
dayTo = dayTo[:4] + "-" + dayTo[4:6] + "-" + dayTo[6:]
}
this.Data["bill"] = maps.Map{
"id": bill.Id,
"isPaid": bill.IsPaid,
"month": month,
"dayFrom": dayFrom,
"dayTo": dayTo,
"amount": numberutils.FormatFloat(bill.Amount, 2),
"typeName": bill.TypeName,
"user": userMap,
"description": bill.Description,
"code": bill.Code,
"canPay": bill.CanPay,
"pricePeriodName": userconfigs.PricePeriodName(bill.PricePeriod),
"pricePeriod": bill.PricePeriod,
}
// 子账单
var serverBillMaps = []maps.Map{}
if (bill.Type == "traffic" || bill.Type == "trafficAndBandwidth") && bill.User != nil {
countResp, err := this.RPC().ServerBillRPC().CountAllServerBills(this.AdminContext(), &pb.CountAllServerBillsRequest{
UserId: bill.User.Id,
Month: bill.Month,
})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
var page = this.NewPage(count)
this.Data["page"] = page.AsHTML()
serverBillsResp, err := this.RPC().ServerBillRPC().ListServerBills(this.AdminContext(), &pb.ListServerBillsRequest{
UserId: bill.User.Id,
Month: bill.Month,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
for _, serverBill := range serverBillsResp.ServerBills {
// server
var serverMap = maps.Map{"id": 0}
if serverBill.Server != nil {
serverMap = maps.Map{
"id": serverBill.Server.Id,
"name": serverBill.Server.Name,
}
}
// plan
var planMap = maps.Map{"id": 0}
if serverBill.Plan != nil {
planMap = maps.Map{
"id": serverBill.Plan.Id,
"name": serverBill.Plan.Name,
"priceType": serverBill.Plan.PriceType,
}
}
serverBillMaps = append(serverBillMaps, maps.Map{
"id": serverBill.Id,
"server": serverMap,
"plan": planMap,
"traffic": numberutils.FormatBytes(serverBill.TotalTrafficBytes),
"bandwidthPercentile": serverBill.BandwidthPercentile,
"bandwidthPercentileSize": numberutils.FormatBytes(serverBill.BandwidthPercentileBytes),
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", serverBill.CreatedAt),
"amount": numberutils.FormatFloat(serverBill.Amount, 2),
"priceType": serverBill.PriceType,
})
}
}
this.Data["serverBills"] = serverBillMaps
// traffic bills
var trafficBillMaps = []maps.Map{}
trafficBillsResp, err := this.RPC().UserTrafficBillRPC().FindUserTrafficBills(this.AdminContext(), &pb.FindUserTrafficBillsRequest{UserBillId: bill.Id})
if err != nil {
this.ErrorPage(err)
return
}
for _, trafficBill := range trafficBillsResp.UserTrafficBills {
var regionMap = maps.Map{"id": 0}
var nodeRegion = trafficBill.NodeRegion
if nodeRegion != nil {
regionMap = maps.Map{
"id": nodeRegion.Id,
"name": nodeRegion.Name,
}
} else if trafficBill.NodeRegionId > 0 {
regionMap = maps.Map{
"id": trafficBill.NodeRegionId,
"name": "[" + this.Lang(codes.AdminCommon_Canceled) + "]",
}
}
trafficBillMaps = append(trafficBillMaps, maps.Map{
"priceType": trafficBill.PriceType,
"priceTypeName": userconfigs.PriceTypeName(trafficBill.PriceType),
"trafficGB": numberutils.FormatBytes(int64(trafficBill.TrafficGB * (1 << 30))),
"trafficPackageGB": numberutils.FormatBytes(int64(trafficBill.TrafficPackageGB * (1 << 30))),
"bandwidthMB": numberutils.FormatBits(int64(trafficBill.BandwidthMB * (1 << 20))),
"bandwidthPercentile": trafficBill.BandwidthPercentile,
"amount": numberutils.FormatFloat(trafficBill.Amount, 2),
"pricePerUnit": trafficBill.PricePerUnit,
"region": regionMap,
})
}
this.Data["trafficBills"] = trafficBillMaps
this.Show()
}

View File

@@ -0,0 +1,39 @@
package bills
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type GenerateAction struct {
actionutils.ParentAction
}
func (this *GenerateAction) Init() {
this.Nav("", "", "generate")
}
func (this *GenerateAction) RunGet(params struct{}) {
this.Data["month"] = timeutil.Format("Ym", time.Now().AddDate(0, -1, 0))
this.Show()
}
func (this *GenerateAction) RunPost(params struct {
Month string
Must *actions.Must
}) {
defer this.CreateLogInfo(codes.Finance_LogBillGenerateManually, params.Month)
_, err := this.RPC().UserBillRPC().GenerateAllUserBills(this.AdminContext(), &pb.GenerateAllUserBillsRequest{Month: params.Month})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,93 @@
package bills
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/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "index")
}
func (this *IndexAction) RunGet(params struct {
PaidFlag int32 `default:"-1"`
UserId int64
Month string
}) {
countResp, err := this.RPC().UserBillRPC().CountAllUserBills(this.AdminContext(), &pb.CountAllUserBillsRequest{
PaidFlag: params.PaidFlag,
UserId: params.UserId,
Month: params.Month,
})
if err != nil {
this.ErrorPage(err)
return
}
var page = this.NewPage(countResp.Count)
this.Data["page"] = page.AsHTML()
billsResp, err := this.RPC().UserBillRPC().ListUserBills(this.AdminContext(), &pb.ListUserBillsRequest{
PaidFlag: params.PaidFlag,
UserId: params.UserId,
Month: params.Month,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var billMaps = []maps.Map{}
for _, bill := range billsResp.UserBills {
var userMap maps.Map = nil
if bill.User != nil {
userMap = maps.Map{
"id": bill.User.Id,
"fullname": bill.User.Fullname,
"username": bill.User.Username,
}
}
var month = bill.Month
var dayFrom = bill.DayFrom
var dayTo = bill.DayTo
if len(month) == 6 {
month = month[:4] + "-" + month[4:]
}
if len(dayFrom) == 8 {
dayFrom = dayFrom[:4] + "-" + dayFrom[4:6] + "-" + dayFrom[6:]
}
if len(dayTo) == 8 {
dayTo = dayTo[:4] + "-" + dayTo[4:6] + "-" + dayTo[6:]
}
billMaps = append(billMaps, maps.Map{
"id": bill.Id,
"isPaid": bill.IsPaid,
"month": month,
"dayFrom": dayFrom,
"dayTo": dayTo,
"amount": numberutils.FormatFloat(bill.Amount, 2),
"typeName": bill.TypeName,
"user": userMap,
"description": bill.Description,
"code": bill.Code,
"canPay": bill.CanPay,
"pricePeriodName": userconfigs.PricePeriodName(bill.PricePeriod),
"pricePeriod": bill.PricePeriod,
"isOverdue": bill.IsOverdue,
})
}
this.Data["bills"] = billMaps
this.Show()
}

View File

@@ -0,0 +1,28 @@
//go:build plus
package bills
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(plus.NewHelper(plus.ComponentCodeFinance)).
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeFinance)).
Data("teaMenu", "finance").
Data("teaSubMenu", "bills").
// 财务管理
Prefix("/finance/bills").
Get("", new(IndexAction)).
GetPost("/generate", new(GenerateAction)).
Post("/pay", new(PayAction)).
Get("/bill", new(BillAction)).
EndAll()
})
}

View File

@@ -0,0 +1,58 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package bills
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/types"
)
type PayAction struct {
actionutils.ParentAction
}
func (this *PayAction) RunPost(params struct {
BillId int64
}) {
defer this.CreateLogInfo(codes.UserBill_LogPayUserBill, params.BillId)
billResp, err := this.RPC().UserBillRPC().FindUserBill(this.AdminContext(), &pb.FindUserBillRequest{UserBillId: params.BillId})
if err != nil {
this.ErrorPage(err)
return
}
var bill = billResp.UserBill
if bill == nil {
this.Fail("找不到要支付的账单")
}
if bill.IsPaid {
this.Fail("此账单已支付,不需要重复支付")
}
// 账号余额
accountResp, err := this.RPC().UserAccountRPC().FindEnabledUserAccountWithUserId(this.AdminContext(), &pb.FindEnabledUserAccountWithUserIdRequest{UserId: bill.User.Id})
if err != nil {
this.ErrorPage(err)
return
}
var account = accountResp.UserAccount
if account == nil {
this.Fail("当前用户还没有账号")
}
if account.Total < bill.Amount {
this.Fail("账号余额不足,当前余额:" + types.String(account.Total) + ",需要支付:" + types.String(bill.Amount))
}
// 支付
_, err = this.RPC().UserBillRPC().PayUserBill(this.AdminContext(), &pb.PayUserBillRequest{UserBillId: params.BillId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,82 @@
//go:build plus
package fee
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/financeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/maps"
)
type BandwidthAction struct {
actionutils.ParentAction
}
func (this *BandwidthAction) Init() {
this.Nav("", "", "bandwidth")
}
func (this *BandwidthAction) RunGet(params struct{}) {
// 财务相关配置
config, err := financeutils.ReadPriceConfig(this.AdminContext())
if err != nil {
this.ErrorPage(err)
return
}
this.Data["config"] = config
// 所有价格项目
itemsResp, err := this.RPC().NodePriceItemRPC().FindAllAvailableNodePriceItems(this.AdminContext(), &pb.FindAllAvailableNodePriceItemsRequest{Type: userconfigs.PriceTypeBandwidth})
if err != nil {
this.ErrorPage(err)
return
}
var itemMaps = []maps.Map{}
for _, item := range itemsResp.NodePriceItems {
var maxSize string
if item.BitsTo == 0 {
maxSize = "∞"
} else {
maxSize = numberutils.FormatBits(item.BitsTo)
}
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"name": item.Name,
"minSize": numberutils.FormatBits(item.BitsFrom),
"maxSize": maxSize,
})
}
this.Data["items"] = itemMaps
// 所有区域
regionsResp, err := this.RPC().NodeRegionRPC().FindAllAvailableNodeRegions(this.AdminContext(), &pb.FindAllAvailableNodeRegionsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var 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()
}

View File

@@ -0,0 +1,104 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package fee
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/financeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type CalculatorAction struct {
actionutils.ParentAction
}
func (this *CalculatorAction) Init() {
this.Nav("", "", "calculator")
}
func (this *CalculatorAction) RunGet(params struct{}) {
// 财务相关配置
config, err := financeutils.ReadPriceConfig(this.AdminContext())
if err != nil {
this.ErrorPage(err)
return
}
this.Data["config"] = config
// 所有区域
regionsResp, err := this.RPC().NodeRegionRPC().FindAllAvailableNodeRegions(this.AdminContext(), &pb.FindAllAvailableNodeRegionsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var regionMaps = []maps.Map{}
for _, region := range regionsResp.NodeRegions {
regionMaps = append(regionMaps, maps.Map{
"id": region.Id,
"name": region.Name,
})
}
this.Data["regions"] = regionMaps
this.Show()
}
func (this *CalculatorAction) RunPost(params struct {
PriceType string
Traffic float64
TrafficUnit string
Bandwidth float64
BandwidthUnit string
RegionId int64
Must *actions.Must
}) {
var trafficGB float64 = 0
var bandwidthMB float64 = 0
switch params.PriceType {
case userconfigs.PriceTypeTraffic:
switch params.TrafficUnit {
case "gb":
trafficGB = params.Traffic
case "tb":
trafficGB = params.Traffic * (1 << 10)
case "eb":
trafficGB = params.Traffic * (1 << 20)
}
case userconfigs.PriceTypeBandwidth:
switch params.BandwidthUnit {
case "mb":
bandwidthMB = params.Bandwidth
case "gb":
bandwidthMB = params.Bandwidth * (1 << 10)
case "tb":
bandwidthMB = params.Bandwidth * (1 << 20)
}
default:
this.Fail("请选择正确的计费类型")
return
}
resp, err := this.RPC().PriceRPC().CalculatePrice(this.AdminContext(), &pb.CalculatePriceRequest{
PriceType: params.PriceType,
TrafficGB: trafficGB,
BandwidthMB: bandwidthMB,
NodeRegionId: params.RegionId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["amount"] = resp.Amount
this.Data["amountFormatted"] = numberutils.FormatFloat(resp.Amount, 2)
this.Data["hasRegionPrice"] = resp.HasNodeRegionPrice
this.Success()
}

View File

@@ -0,0 +1,188 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package fee
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/financeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/actions"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "basic")
}
func (this *IndexAction) RunGet(params struct{}) {
config, err := financeutils.ReadPriceConfig(this.AdminContext())
if err != nil {
this.ErrorPage(err)
return
}
this.Data["config"] = config
this.Show()
}
func (this *IndexAction) RunPost(params struct {
IsOn bool
PriceType string
PricePeriod string
TrafficPriceJSON []byte
BandwidthPriceJSON []byte
UserCanChangePriceType bool
UserCanChangePricePeriod bool
EnablePlans bool
ShowPlansInUserSystem bool
EnableTrafficPackages bool
ShowTrafficPackages bool
UserUIShowPrices bool
UnpaidBillPolicyIsOn bool
UnpaidBillPolicyMinDailyBillDays int32
UnpaidBillPolicyMinMonthlyBillDays int32
UnpaidBillPolicyDisableServers bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.FinanceFee_LogUpdateFeeSetting)
valueResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeUserPriceConfig})
if err != nil {
this.ErrorPage(err)
return
}
var valueJSON = valueResp.ValueJSON
var priceConfig = userconfigs.DefaultUserPriceConfig()
if len(valueJSON) > 0 {
err = json.Unmarshal(valueJSON, priceConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
priceConfig.IsOn = params.IsOn
if !userconfigs.IsValidPricePeriod(params.PricePeriod) {
this.Fail("错误的结算周期 '" + params.PricePeriod + "'")
return
}
priceConfig.DefaultPricePeriod = params.PricePeriod
priceConfig.UserCanChangePriceType = params.UserCanChangePriceType
priceConfig.UserCanChangePricePeriod = params.UserCanChangePricePeriod
priceConfig.EnablePlans = params.EnablePlans
priceConfig.ShowPlansInUserSystem = params.ShowPlansInUserSystem
priceConfig.EnableTrafficPackages = params.EnableTrafficPackages
priceConfig.ShowTrafficPackages = params.ShowTrafficPackages
priceConfig.UserUI.ShowPrices = params.UserUIShowPrices
priceConfig.DefaultPriceType = params.PriceType
switch params.PriceType {
case serverconfigs.PlanPriceTypeTraffic:
if len(params.TrafficPriceJSON) == 0 {
this.Fail("请设置流量价格")
}
case serverconfigs.PlanPriceTypeBandwidth:
if len(params.BandwidthPriceJSON) == 0 {
this.Fail("请设置带宽价格")
}
default:
this.Fail("请选择默认计费方式")
return
}
// 流量价格
if len(params.TrafficPriceJSON) > 0 {
var config = &serverconfigs.PlanTrafficPriceConfig{}
err := json.Unmarshal(params.TrafficPriceJSON, config)
if err != nil {
this.Fail("流量价格设置错误:" + err.Error())
}
if params.PriceType == serverconfigs.PlanPriceTypeTraffic || params.UserCanChangePriceType {
if config.Base <= 0 && len(config.Ranges) == 0 {
this.Fail("流量基础价格和流量阶梯价格必须至少填写一个")
}
}
priceConfig.DefaultTrafficPriceConfig = config
}
// 带宽价格
if len(params.BandwidthPriceJSON) > 0 {
var config = &serverconfigs.PlanBandwidthPriceConfig{}
err := json.Unmarshal(params.BandwidthPriceJSON, config)
if err != nil {
this.Fail("带宽价格设置错误:" + err.Error())
}
if params.PriceType == serverconfigs.PlanPriceTypeBandwidth || params.UserCanChangePriceType {
if config.Percentile <= 0 {
this.Fail("带宽百分位必须大于0")
}
if config.Percentile > 100 {
this.Fail("带宽百分位必须不大于100")
}
if config.Base <= 0 && len(config.Ranges) == 0 {
this.Fail("带宽基础价格和带宽阶梯价格必须至少填写一个")
}
}
priceConfig.DefaultBandwidthPriceConfig = config
// 同步修改UserUI中的用量统计设置
uiConfig, err := configloaders.LoadUserUIConfig()
if err != nil {
this.ErrorPage(err)
return
}
if uiConfig != nil && uiConfig.TrafficStats.BandwidthAlgo != config.BandwidthAlgo {
uiConfig.TrafficStats.BandwidthAlgo = config.BandwidthAlgo
err = configloaders.UpdateUserUIConfig(uiConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
}
// 未支付账单策略
priceConfig.UnpaidBillPolicy.IsOn = params.UnpaidBillPolicyIsOn
priceConfig.UnpaidBillPolicy.MinDailyBillDays = params.UnpaidBillPolicyMinDailyBillDays
priceConfig.UnpaidBillPolicy.MinMonthlyBillDays = params.UnpaidBillPolicyMinMonthlyBillDays
priceConfig.UnpaidBillPolicy.DisableServers = params.UnpaidBillPolicyDisableServers
financeConfigJSON, err := json.Marshal(priceConfig)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().SysSettingRPC().UpdateSysSetting(this.AdminContext(), &pb.UpdateSysSettingRequest{
Code: systemconfigs.SettingCodeUserPriceConfig,
ValueJSON: financeConfigJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
// 重置配置
configloaders.ResetUserPriceConfig(priceConfig)
this.Success()
}

View File

@@ -0,0 +1,54 @@
//go:build plus
package items
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/actions"
)
type CreateBandwidthPopupAction struct {
actionutils.ParentAction
}
func (this *CreateBandwidthPopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreateBandwidthPopupAction) RunGet(params struct {
}) {
this.Show()
}
func (this *CreateBandwidthPopupAction) RunPost(params struct {
Name string
MinMB uint32
MaxMB uint32
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入名称")
if params.MinMB > params.MaxMB {
params.MinMB, params.MaxMB = params.MaxMB, params.MinMB
}
createResp, err := this.RPC().NodePriceItemRPC().CreateNodePriceItem(this.AdminContext(), &pb.CreateNodePriceItemRequest{
Name: params.Name,
Type: userconfigs.PriceTypeBandwidth,
BitsFrom: int64(params.MinMB) << 20,
BitsTo: int64(params.MaxMB) << 20,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo(codes.NodePriceItem_LogCreateNodePriceItemBandwidth, createResp.NodePriceItemId)
this.Success()
}

View File

@@ -0,0 +1,54 @@
//go:build plus
package items
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/actions"
)
type CreateTrafficPopupAction struct {
actionutils.ParentAction
}
func (this *CreateTrafficPopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreateTrafficPopupAction) RunGet(params struct {
}) {
this.Show()
}
func (this *CreateTrafficPopupAction) RunPost(params struct {
Name string
MinGB uint32
MaxGB uint32
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入名称")
if params.MinGB > 0 && params.MaxGB > 0 && params.MinGB > params.MaxGB {
params.MinGB, params.MaxGB = params.MaxGB, params.MinGB
}
createResp, err := this.RPC().NodePriceItemRPC().CreateNodePriceItem(this.AdminContext(), &pb.CreateNodePriceItemRequest{
Name: params.Name,
Type: userconfigs.PriceTypeTraffic,
BitsFrom: int64(params.MinGB) << 33,
BitsTo: int64(params.MaxGB) << 33,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo(codes.NodePriceItem_LogCreateNodePriceItemTraffic, createResp.NodePriceItemId)
this.Success()
}

View File

@@ -0,0 +1,27 @@
//go:build plus
package items
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
ItemId int64
}) {
defer this.CreateLogInfo(codes.NodePriceItem_LogDeleteNodePriceItem, params.ItemId)
_, err := this.RPC().NodePriceItemRPC().DeleteNodePriceItem(this.AdminContext(), &pb.DeleteNodePriceItemRequest{NodePriceItemId: params.ItemId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,25 @@
//go:build plus
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.AdminModuleCodeFinance)).
Data("teaMenu", "clusters").
Data("teaSubMenu", "region").
Prefix("/finance/fee/items").
GetPost("/createTrafficPopup", new(CreateTrafficPopupAction)).
GetPost("/updateTrafficPopup", new(UpdateTrafficPopupAction)).
GetPost("/createBandwidthPopup", new(CreateBandwidthPopupAction)).
GetPost("/updateBandwidthPopup", new(UpdateBandwidthPopupAction)).
Post("/delete", new(DeleteAction)).
EndAll()
})
}

View File

@@ -0,0 +1,76 @@
//go:build plus
package items
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdateBandwidthPopupAction struct {
actionutils.ParentAction
}
func (this *UpdateBandwidthPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdateBandwidthPopupAction) 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
}
var item = itemResp.NodePriceItem
if item == nil {
this.NotFound("nodePriceItem", params.ItemId)
return
}
this.Data["item"] = maps.Map{
"id": item.Id,
"name": item.Name,
"minMB": item.BitsFrom / (1 << 20),
"maxMB": item.BitsTo / (1 << 20),
}
this.Show()
}
func (this *UpdateBandwidthPopupAction) RunPost(params struct {
ItemId int64
Name string
MinMB uint32
MaxMB uint32
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.NodePriceItem_LogUpdateNodePriceItemBandwidth, params.ItemId)
params.Must.
Field("name", params.Name).
Require("请输入名称")
if params.MinMB > 0 && params.MaxMB > 0 && params.MinMB > params.MaxMB {
params.MinMB, params.MaxMB = params.MaxMB, params.MinMB
}
_, err := this.RPC().NodePriceItemRPC().UpdateNodePriceItem(this.AdminContext(), &pb.UpdateNodePriceItemRequest{
NodePriceItemId: params.ItemId,
Name: params.Name,
BitsFrom: int64(params.MinMB) << 20,
BitsTo: int64(params.MaxMB) << 20,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,76 @@
//go:build plus
package items
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdateTrafficPopupAction struct {
actionutils.ParentAction
}
func (this *UpdateTrafficPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdateTrafficPopupAction) 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
}
var item = itemResp.NodePriceItem
if item == nil {
this.NotFound("nodePriceItem", params.ItemId)
return
}
this.Data["item"] = maps.Map{
"id": item.Id,
"name": item.Name,
"minGB": item.BitsFrom / (1 << 33),
"maxGB": item.BitsTo / (1 << 33),
}
this.Show()
}
func (this *UpdateTrafficPopupAction) RunPost(params struct {
ItemId int64
Name string
MinGB uint32
MaxGB uint32
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.NodePriceItem_LogUpdateNodePriceItemTraffic, params.ItemId)
params.Must.
Field("name", params.Name).
Require("请输入名称")
if params.MinGB > 0 && params.MaxGB > 0 && params.MinGB > params.MaxGB {
params.MinGB, params.MaxGB = params.MaxGB, params.MinGB
}
_, err := this.RPC().NodePriceItemRPC().UpdateNodePriceItem(this.AdminContext(), &pb.UpdateNodePriceItemRequest{
NodePriceItemId: params.ItemId,
Name: params.Name,
BitsFrom: int64(params.MinGB) << 33,
BitsTo: int64(params.MaxGB) << 33,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,82 @@
//go:build plus
package fee
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/financeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/maps"
)
type TrafficAction struct {
actionutils.ParentAction
}
func (this *TrafficAction) Init() {
this.Nav("", "", "traffic")
}
func (this *TrafficAction) RunGet(params struct{}) {
// 财务相关配置
config, err := financeutils.ReadPriceConfig(this.AdminContext())
if err != nil {
this.ErrorPage(err)
return
}
this.Data["config"] = config
// 所有价格项目
itemsResp, err := this.RPC().NodePriceItemRPC().FindAllAvailableNodePriceItems(this.AdminContext(), &pb.FindAllAvailableNodePriceItemsRequest{Type: userconfigs.PriceTypeTraffic})
if err != nil {
this.ErrorPage(err)
return
}
var itemMaps = []maps.Map{}
for _, item := range itemsResp.NodePriceItems {
var maxSize string
if item.BitsTo == 0 {
maxSize = "∞"
} else {
maxSize = numberutils.FormatBytes(item.BitsTo / 8)
}
itemMaps = append(itemMaps, maps.Map{
"id": item.Id,
"name": item.Name,
"minSize": numberutils.FormatBytes(item.BitsFrom / 8),
"maxSize": maxSize,
})
}
this.Data["items"] = itemMaps
// 所有区域
regionsResp, err := this.RPC().NodeRegionRPC().FindAllAvailableNodeRegions(this.AdminContext(), &pb.FindAllAvailableNodeRegionsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var 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()
}

View File

@@ -0,0 +1,119 @@
//go:build plus
package fee
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"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
}
var 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,
}
// 当前价格
var 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
}
var item = itemResp.NodePriceItem
if item == nil {
this.NotFound("nodePriceItem", params.ItemId)
return
}
var minSize = ""
var maxSize = ""
switch item.Type {
case userconfigs.PriceTypeTraffic:
minSize = numberutils.FormatBytes(item.BitsFrom / 8)
if item.BitsTo > 0 {
maxSize = numberutils.FormatBytes(item.BitsTo / 8)
} else {
maxSize = "∞"
}
case userconfigs.PriceTypeBandwidth:
minSize = numberutils.FormatBits(item.BitsFrom)
if item.BitsTo > 0 {
maxSize = numberutils.FormatBits(item.BitsTo)
} else {
maxSize = "∞"
}
}
this.Data["item"] = maps.Map{
"id": item.Id,
"name": item.Name,
"type": item.Type,
"minSize": minSize,
"maxSize": maxSize,
}
this.Show()
}
func (this *UpdatePricePopupAction) RunPost(params struct {
RegionId int64
ItemId int64
Price float32
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.NodeRegionPrice_LogUpdateNodeRegionPrice, 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()
}

View File

@@ -0,0 +1,34 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package financeutils
import (
"context"
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
)
// ReadPriceConfig 读取价格相关配置
func ReadPriceConfig(ctx context.Context) (*userconfigs.UserPriceConfig, error) {
rpcClient, err := rpc.SharedRPC()
if err != nil {
return nil, err
}
valueResp, err := rpcClient.SysSettingRPC().ReadSysSetting(ctx, &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeUserPriceConfig})
if err != nil {
return nil, err
}
var valueJSON = valueResp.ValueJSON
var config = userconfigs.DefaultUserPriceConfig()
if len(valueJSON) > 0 {
err = json.Unmarshal(valueJSON, config)
if err != nil {
return nil, err
}
}
return config, nil
}

View File

@@ -0,0 +1,55 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package income
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type DailyAction struct {
actionutils.ParentAction
}
func (this *DailyAction) Init() {
this.Nav("", "", "monthly")
}
func (this *DailyAction) RunGet(params struct {
Month string
}) {
if len(params.Month) == 0 {
params.Month = timeutil.Format("Y-m")
}
this.Data["month"] = params.Month
var dayFrom = params.Month + "-01"
var dayTo = params.Month + "-32"
statsResp, err := this.RPC().UserAccountDailyStatRPC().ListUserAccountDailyStats(this.AdminContext(), &pb.ListUserAccountDailyStatsRequest{
DayFrom: dayFrom,
DayTo: dayTo,
})
if err != nil {
this.ErrorPage(err)
return
}
var statMaps = []maps.Map{}
for _, stat := range statsResp.Stats {
if stat.Day > timeutil.Format("Ymd") {
continue
}
statMaps = append(statMaps, maps.Map{
"day": stat.Day[:4] + "-" + stat.Day[4:6] + "-" + stat.Day[6:],
"income": stat.Income,
"expense": stat.Expense,
})
}
this.Data["stats"] = statMaps
this.Show()
}

View File

@@ -0,0 +1,58 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package income
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "daily")
}
func (this *IndexAction) RunGet(params struct {
DayFrom string
DayTo string
}) {
var dayFrom = params.DayFrom
var dayTo = params.DayTo
if len(dayFrom) == 0 {
dayFrom = timeutil.Format("Y-m-d", time.Now().AddDate(0, 0, -14))
}
if len(dayTo) == 0 {
dayTo = timeutil.Format("Y-m-d")
}
if dayFrom > dayTo {
dayFrom, dayTo = dayTo, dayFrom
}
this.Data["dayFrom"] = dayFrom
this.Data["dayTo"] = dayTo
statsResp, err := this.RPC().UserAccountDailyStatRPC().ListUserAccountDailyStats(this.AdminContext(), &pb.ListUserAccountDailyStatsRequest{
DayFrom: dayFrom,
DayTo: dayTo,
})
if err != nil {
this.ErrorPage(err)
return
}
var statMaps = []maps.Map{}
for _, stat := range statsResp.Stats {
statMaps = append(statMaps, maps.Map{
"day": stat.Day[:4] + "-" + stat.Day[4:6] + "-" + stat.Day[6:],
"income": stat.Income,
"expense": stat.Expense,
})
}
this.Data["stats"] = statMaps
this.Show()
}

View File

@@ -0,0 +1,29 @@
//go:build plus
package income
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(plus.NewHelper(plus.ComponentCodeFinance)).
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeFinance)).
Data("teaMenu", "finance").
Data("teaSubMenu", "income").
// 财务管理
Prefix("/finance/income").
Get("", new(IndexAction)).
Get("/monthly", new(MonthlyAction)).
Get("/daily", new(DailyAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,58 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package income
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type MonthlyAction struct {
actionutils.ParentAction
}
func (this *MonthlyAction) Init() {
this.Nav("", "", "monthly")
}
func (this *MonthlyAction) RunGet(params struct {
DayFrom string
DayTo string
}) {
var dayFrom = params.DayFrom
var dayTo = params.DayTo
if len(dayFrom) == 0 {
dayFrom = timeutil.Format("Y-m-01", time.Now().AddDate(0, -12, 0))
}
if len(dayTo) == 0 {
dayTo = timeutil.Format("Y-m-d")
}
if dayFrom > dayTo {
dayFrom, dayTo = dayTo, dayFrom
}
this.Data["dayFrom"] = dayFrom
this.Data["dayTo"] = dayTo
statsResp, err := this.RPC().UserAccountDailyStatRPC().ListUserAccountMonthlyStats(this.AdminContext(), &pb.ListUserAccountMonthlyStatsRequest{
DayFrom: dayFrom,
DayTo: dayTo,
})
if err != nil {
this.ErrorPage(err)
return
}
var statMaps = []maps.Map{}
for _, stat := range statsResp.Stats {
statMaps = append(statMaps, maps.Map{
"month": stat.Month[:4] + "-" + stat.Month[4:6],
"income": stat.Income,
"expense": stat.Expense,
})
}
this.Data["stats"] = statMaps
this.Show()
}

View File

@@ -0,0 +1,16 @@
package finance
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
// TODO 暂时先跳转到账单页将来做成Dashboard
this.RedirectURL("/finance/bills")
}

View File

@@ -0,0 +1,41 @@
//go:build plus
package finance
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/fee"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/users"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(plus.NewHelper(plus.ComponentCodeFinance)).
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeFinance)).
// 财务管理
Prefix("/finance").
Get("", new(IndexAction)).
Data("teaMenu", "finance").
// 计费设置
Prefix("/finance/fee").
Data("teaSubMenu", "fee").
GetPost("", new(fee.IndexAction)).
Get("/traffic", new(fee.TrafficAction)).
Get("/bandwidth", new(fee.BandwidthAction)).
GetPost("/updatePricePopup", new(fee.UpdatePricePopupAction)).
GetPost("/calculator", new(fee.CalculatorAction)).
// 用户
Prefix("/finance/users").
Post("/options", new(users.OptionsAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,84 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package logs
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/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct {
Keyword string
EventType string
}) {
this.Data["keyword"] = params.Keyword
this.Data["eventType"] = params.EventType
// 所有事件类型
this.Data["events"] = userconfigs.FindAllAccountEventTypes()
// 数量
countResp, err := this.RPC().UserAccountLogRPC().CountUserAccountLogs(this.AdminContext(), &pb.CountUserAccountLogsRequest{
Keyword: params.Keyword,
EventType: params.EventType,
})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
var page = this.NewPage(count)
this.Data["page"] = page.AsHTML()
logsResp, err := this.RPC().UserAccountLogRPC().ListUserAccountLogs(this.AdminContext(), &pb.ListUserAccountLogsRequest{
Keyword: params.Keyword,
EventType: params.EventType,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var logMaps = []maps.Map{}
for _, log := range logsResp.UserAccountLogs {
var userMap = maps.Map{}
if log.User != nil {
userMap = maps.Map{
"id": log.User.Id,
"username": log.User.Username,
"fullname": log.User.Fullname,
}
}
logMaps = append(logMaps, maps.Map{
"id": log.Id,
"description": log.Description,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", log.CreatedAt),
"delta": log.Delta,
"deltaFormatted": numberutils.FormatFloat(log.Delta, 2),
"deltaFrozen": log.DeltaFrozen,
"total": log.Total,
"totalFormatted": numberutils.FormatFloat(log.Total, 2),
"totalFrozen": log.TotalFrozen,
"event": userconfigs.FindAccountEvent(log.EventType),
"user": userMap,
"accountId": log.UserAccountId,
})
}
this.Data["logs"] = logMaps
this.Show()
}

View File

@@ -0,0 +1,27 @@
//go:build plus
package logs
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(plus.NewHelper(plus.ComponentCodeFinance)).
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeFinance)).
Data("teaMenu", "finance").
Data("teaSubMenu", "logs").
// 财务管理
Prefix("/finance/logs").
Get("", new(IndexAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,109 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package orders
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/orders/orderutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "order")
}
func (this *IndexAction) RunGet(params struct {
UserId int64
Status string
Keyword string
}) {
this.Data["userId"] = params.UserId
this.Data["status"] = params.Status
this.Data["keyword"] = params.Keyword
// 是否支持支付
orderConfig, err := orderutils.FindOrderConfig(this.AdminContext())
if err != nil {
this.ErrorPage(err)
return
}
this.Data["enablePay"] = orderConfig.EnablePay
// 订单数量
countResp, err := this.RPC().UserOrderRPC().CountEnabledUserOrders(this.AdminContext(), &pb.CountEnabledUserOrdersRequest{
UserId: params.UserId,
Status: params.Status,
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
var page = this.NewPage(count)
this.Data["page"] = page.AsHTML()
// 订单列表
ordersResp, err := this.RPC().UserOrderRPC().ListEnabledUserOrders(this.AdminContext(), &pb.ListEnabledUserOrdersRequest{
UserId: params.UserId,
Status: params.Status,
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var orderMaps = []maps.Map{}
for _, order := range ordersResp.UserOrders {
// user
var userMap = maps.Map{
"id": 0,
}
if order.User != nil {
userMap = maps.Map{
"id": order.User.Id,
"fullname": order.User.Fullname,
"username": order.User.Username,
}
}
// method
var methodMap = maps.Map{
"id": 0,
}
if order.OrderMethod != nil {
methodMap = maps.Map{
"id": order.OrderMethod.Id,
"name": order.OrderMethod.Name,
}
}
orderMaps = append(orderMaps, maps.Map{
"code": order.Code,
"type": order.Type,
"typeName": userconfigs.FindOrderTypeName(order.Type),
"status": order.Status,
"statusName": userconfigs.FindOrderStatusName(order.Status),
"amount": order.Amount,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", order.CreatedAt),
"user": userMap,
"method": methodMap,
})
}
this.Data["orders"] = orderMaps
// 所有状态
this.Data["statusList"] = userconfigs.FindAllOrderStatusList()
this.Show()
}

View File

@@ -0,0 +1,47 @@
//go:build plus
package orders
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/orders/methods"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/orders/methods/method"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/orders/order"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(plus.NewHelper(plus.ComponentCodeFinance)).
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeFinance)).
Data("teaMenu", "finance").
Data("teaSubMenu", "orders").
// 订单列表
Prefix("/finance/orders").
Get("", new(IndexAction)).
GetPost("/setting", new(SettingAction)).
// 单个订单
Prefix("/finance/orders/order").
Get("", new(order.IndexAction)).
Post("/finish", new(order.FinishAction)).
// 支付方式列表
Prefix("/finance/orders/methods").
Get("", new(methods.IndexAction)).
GetPost("/createPopup", new(methods.CreatePopupAction)).
// 单个支付方式
Prefix("/finance/orders/methods/method").
Get("", new(method.IndexAction)).
GetPost("/update", new(method.UpdateAction)).
Post("/delete", new(method.DeleteAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,142 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package methods
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/actions"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "method")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Data["presetMethods"] = userconfigs.FindAllPresetPayMethods()
this.Data["clientTypes"] = userconfigs.FindAllPayClientTypes()
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
Code string
Description string
ClientType string
QrcodeTitle string
ParentCode string
// 自定义支付方式
Url string
// Alipay
ParamsAlipayAppId string
ParamsAlipayPrivateKey string
ParamsAlipayAppPublicCert string
ParamsAlipayPublicCert string
ParamsAlipayRootCert string
ParamsAlipayProductCode string
ParamsAlipayIsSandbox bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
var methodId int64
defer func() {
this.CreateLogInfo(codes.OrderMethod_LogCreateOrderMethod, methodId)
}()
params.Must.
Field("name", params.Name).
Require("请输入支付方式名称").
Field("code", params.Code).
Require("请输入支付方式代号").
Match("^\\w+$", "代号中只能包含数字、字母和下划线").
Field("description", params.Description).
Require("请输入支付方式描述")
// 检查代号是否被占用
methodResp, err := this.RPC().OrderMethodRPC().FindEnabledOrderMethodWithCode(this.AdminContext(), &pb.FindEnabledOrderMethodWithCodeRequest{Code: params.Code})
if err != nil {
this.ErrorPage(err)
return
}
if methodResp.OrderMethod != nil {
this.Fail("代号 '" + params.Code + "' 已经被别的支付方式使用,请换一个")
}
var payParams userconfigs.PayMethodParams
switch params.ParentCode {
case "":
params.Must.
Field("url", params.Url).
Require("请输入支付URL").
Match("^(?i)(http|https)://", "请输入正确的支付URL")
case userconfigs.PayMethodAlipay:
params.Must.
Field("paramsAlipayAppId", params.ParamsAlipayAppId).
Require("请输入APPID").
Field("paramsAlipayPrivateKey", params.ParamsAlipayPrivateKey).
Require("请输入私钥").
Field("paramsAlipayAppPublicCert", params.ParamsAlipayAppPublicCert).
Require("请输入应用公钥证书").
Field("paramsAlipayPublicCert", params.ParamsAlipayPublicCert).
Require("请输入支付宝公钥证书").
Field("paramsAlipayRootCert", params.ParamsAlipayRootCert).
Require("请输入支付宝根证书").
Field("paramsAlipayProductCode", params.ParamsAlipayProductCode).
Require("请输入产品代号")
payParams = &userconfigs.AlipayPayMethodParams{
IsSandbox: params.ParamsAlipayIsSandbox,
AppId: params.ParamsAlipayAppId,
PrivateKey: params.ParamsAlipayPrivateKey,
AppPublicCert: params.ParamsAlipayAppPublicCert,
AlipayPublicCert: params.ParamsAlipayPublicCert,
AlipayRootCert: params.ParamsAlipayRootCert,
ProductCode: params.ParamsAlipayProductCode,
}
}
if payParams != nil {
err = payParams.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())
return
}
}
payParamsJSON, err := json.Marshal(payParams)
if err != nil {
this.ErrorPage(err)
return
}
createResp, err := this.RPC().OrderMethodRPC().CreateOrderMethod(this.AdminContext(), &pb.CreateOrderMethodRequest{
Name: params.Name,
Code: params.Code,
Description: params.Description,
Url: params.Url,
ParentCode: params.ParentCode,
ParamsJSON: payParamsJSON,
ClientType: params.ClientType,
QrcodeTitle: params.QrcodeTitle,
})
if err != nil {
this.ErrorPage(err)
return
}
methodId = createResp.OrderMethodId
this.Success()
}

View File

@@ -0,0 +1,64 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package methods
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/orders/orderutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "method")
}
func (this *IndexAction) RunGet(params struct{}) {
// 是否支持支付
orderConfig, err := orderutils.FindOrderConfig(this.AdminContext())
if err != nil {
this.ErrorPage(err)
return
}
this.Data["enablePay"] = orderConfig.EnablePay
// 所有的支付方式
methodsResp, err := this.RPC().OrderMethodRPC().FindAllEnabledOrderMethods(this.AdminContext(), &pb.FindAllEnabledOrderMethodsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var methodMaps = []maps.Map{}
for _, method := range methodsResp.OrderMethods {
var parentName = ""
if len(method.ParentCode) > 0 {
var parentMethod = userconfigs.FindPresetPayMethodWithCode(method.ParentCode)
if parentMethod != nil {
parentName = parentMethod.Name
}
}
methodMaps = append(methodMaps, maps.Map{
"id": method.Id,
"name": method.Name,
"code": method.Code,
"parentCode": method.ParentCode,
"parentName": parentName,
"description": method.Description,
"url": method.Url,
"isOn": method.IsOn,
"clientType": method.ClientType,
"clientTypeName": userconfigs.FindPayClientTypeName(method.ClientType),
})
}
this.Data["methods"] = methodMaps
this.Show()
}

View File

@@ -0,0 +1,32 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package method
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) Init() {
this.Nav("", "", "")
}
func (this *DeleteAction) RunPost(params struct {
MethodId int64
}) {
defer this.CreateLogInfo(codes.OrderMethod_LogDeleteOrderMethod, params.MethodId)
_, err := this.RPC().OrderMethodRPC().DeleteOrderMethod(this.AdminContext(), &pb.DeleteOrderMethodRequest{OrderMethodId: params.MethodId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,97 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package method
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/maps"
"strings"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "index")
}
func (this *IndexAction) RunGet(params struct {
MethodId int64
}) {
methodResp, err := this.RPC().OrderMethodRPC().FindEnabledOrderMethod(this.AdminContext(), &pb.FindEnabledOrderMethodRequest{OrderMethodId: params.MethodId})
if err != nil {
this.ErrorPage(err)
return
}
var method = methodResp.OrderMethod
if method == nil {
this.NotFound("orderMethod", params.MethodId)
return
}
// 父级支付方式和参数
var parentCode = method.ParentCode
var parentName = ""
var payParams any
var payNotifyURL = ""
if len(parentCode) > 0 {
var parentDef = userconfigs.FindPresetPayMethodWithCode(parentCode)
if parentDef != nil {
parentName = parentDef.Name
payParams, err = userconfigs.DecodePayMethodParams(parentCode, method.Params)
if err != nil {
this.ErrorPage(err)
return
}
if len(parentDef.NotifyURL) > 0 {
accessAddrResp, err := this.RPC().UserNodeRPC().FindUserNodeAccessAddr(this.AdminContext(), &pb.FindUserNodeAccessAddrRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var accessAddr = accessAddrResp.AccessAddr
payNotifyURL = strings.ReplaceAll(parentDef.NotifyURL, "${baseAddr}", accessAddr)
}
}
}
// 终端
if len(method.ClientType) == 0 {
method.ClientType = userconfigs.PayClientTypeAll
}
var clientTypeName = ""
var clientTypeDescription = ""
var clientType = userconfigs.FindPayClientType(method.ClientType)
if clientType != nil {
clientTypeName = clientType.Name
clientTypeDescription = clientType.Description
}
this.Data["method"] = maps.Map{
"id": method.Id,
"name": method.Name,
"code": method.Code,
"description": method.Description,
"secret": method.Secret,
"isOn": method.IsOn,
"parentCode": parentCode,
"parentName": parentName,
"payNotifyURL": payNotifyURL,
"url": method.Url, // 自定义
"params": payParams, // 非自定义
"clientType": method.ClientType,
"clientTypeName": clientTypeName,
"clientTypeDescription": clientTypeDescription,
"qrcodeTitle": method.QrcodeTitle,
}
this.Show()
}

View File

@@ -0,0 +1,198 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package method
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdateAction struct {
actionutils.ParentAction
}
func (this *UpdateAction) Init() {
this.Nav("", "", "update")
}
func (this *UpdateAction) RunGet(params struct {
MethodId int64
}) {
methodResp, err := this.RPC().OrderMethodRPC().FindEnabledOrderMethod(this.AdminContext(), &pb.FindEnabledOrderMethodRequest{OrderMethodId: params.MethodId})
if err != nil {
this.ErrorPage(err)
return
}
var method = methodResp.OrderMethod
if method == nil {
this.NotFound("orderMethod", params.MethodId)
return
}
// 父级支付方式和参数
var parentCode = method.ParentCode
var parentName = ""
var payParams any
if len(parentCode) > 0 {
var parentDef = userconfigs.FindPresetPayMethodWithCode(parentCode)
if parentDef != nil {
parentName = parentDef.Name
payParams, err = userconfigs.DecodePayMethodParams(parentCode, method.Params)
if err != nil {
this.ErrorPage(err)
return
}
}
}
// 支持的终端类型
this.Data["clientTypes"] = userconfigs.FindAllPayClientTypes()
if len(method.ClientType) == 0 {
method.ClientType = userconfigs.PayClientTypeAll
}
this.Data["method"] = maps.Map{
"id": method.Id,
"name": method.Name,
"code": method.Code,
"description": method.Description,
"url": method.Url,
"isOn": method.IsOn,
"parentCode": method.ParentCode,
"parentName": parentName,
"params": payParams,
"clientType": method.ClientType,
"qrcodeTitle": method.QrcodeTitle,
}
this.Show()
}
func (this *UpdateAction) RunPost(params struct {
MethodId int64
Name string
Code string
Description string
IsOn bool
ClientType string
QrcodeTitle string
// 自定义支付方式
Url string
// Alipay
ParamsAlipayAppId string
ParamsAlipayPrivateKey string
ParamsAlipayAppPublicCert string
ParamsAlipayPublicCert string
ParamsAlipayRootCert string
ParamsAlipayProductCode string
ParamsAlipayIsSandbox bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.OrderMethod_LogUpdateOrderMethod, params.MethodId)
params.Must.
Field("name", params.Name).
Require("请输入支付方式名称").
Field("code", params.Code).
Require("请输入支付方式代号").
Match("^\\w+$", "代号中只能包含数字、字母和下划线").
Field("description", params.Description).
Require("请输入支付方式描述")
// 检查代号是否被占用
methodWithCodeResp, err := this.RPC().OrderMethodRPC().FindEnabledOrderMethodWithCode(this.AdminContext(), &pb.FindEnabledOrderMethodWithCodeRequest{Code: params.Code})
if err != nil {
this.ErrorPage(err)
return
}
if methodWithCodeResp.OrderMethod != nil && methodWithCodeResp.OrderMethod.Id != params.MethodId {
this.Fail("代号 '" + params.Code + "' 已经被别的支付方式使用,请换一个")
}
// 当前支付方式
methodResp, err := this.RPC().OrderMethodRPC().FindEnabledOrderMethod(this.AdminContext(), &pb.FindEnabledOrderMethodRequest{OrderMethodId: params.MethodId})
if err != nil {
this.ErrorPage(err)
return
}
var method = methodResp.OrderMethod
if method == nil {
this.Fail("找不到要修改的支付方式")
}
var payParams userconfigs.PayMethodParams
switch method.ParentCode {
case "":
params.Must.
Field("url", params.Url).
Require("请输入支付URL").
Match("^(?i)(http|https)://", "请输入正确的支付URL")
case userconfigs.PayMethodAlipay:
params.Must.
Field("paramsAlipayAppId", params.ParamsAlipayAppId).
Require("请输入APPID").
Field("paramsAlipayPrivateKey", params.ParamsAlipayPrivateKey).
Require("请输入私钥").
Field("paramsAlipayAppPublicCert", params.ParamsAlipayAppPublicCert).
Require("请输入应用公钥证书").
Field("paramsAlipayPublicCert", params.ParamsAlipayPublicCert).
Require("请输入支付宝公钥证书").
Field("paramsAlipayRootCert", params.ParamsAlipayRootCert).
Require("请输入支付宝根证书").
Field("paramsAlipayProductCode", params.ParamsAlipayProductCode).
Require("请输入产品代号")
payParams = &userconfigs.AlipayPayMethodParams{
IsSandbox: params.ParamsAlipayIsSandbox,
AppId: params.ParamsAlipayAppId,
PrivateKey: params.ParamsAlipayPrivateKey,
AppPublicCert: params.ParamsAlipayAppPublicCert,
AlipayPublicCert: params.ParamsAlipayPublicCert,
AlipayRootCert: params.ParamsAlipayRootCert,
ProductCode: params.ParamsAlipayProductCode,
}
}
if payParams != nil {
err = payParams.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())
return
}
}
payParamsJSON, err := json.Marshal(payParams)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().OrderMethodRPC().UpdateOrderMethod(this.AdminContext(), &pb.UpdateOrderMethodRequest{
OrderMethodId: params.MethodId,
Name: params.Name,
Code: params.Code,
Description: params.Description,
Url: params.Url,
ParamsJSON: payParamsJSON,
IsOn: params.IsOn,
ClientType: params.ClientType,
QrcodeTitle: params.QrcodeTitle,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,28 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package order
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type FinishAction struct {
actionutils.ParentAction
}
func (this *FinishAction) RunPost(params struct {
OrderCode string
}) {
defer this.CreateLogInfo(codes.UserOrder_LogFinishUserOrder, params.OrderCode)
_, err := this.RPC().UserOrderRPC().FinishUserOrder(this.AdminContext(), &pb.FinishUserOrderRequest{Code: params.OrderCode})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,76 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package order
import (
"errors"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "order")
}
func (this *IndexAction) RunGet(params struct {
OrderCode string
}) {
orderResp, err := this.RPC().UserOrderRPC().FindEnabledUserOrder(this.AdminContext(), &pb.FindEnabledUserOrderRequest{Code: params.OrderCode})
if err != nil {
this.ErrorPage(err)
return
}
var order = orderResp.UserOrder
if order == nil {
this.ErrorPage(errors.New("order with code '" + params.OrderCode + "' not found"))
return
}
// user
var userMap = maps.Map{
"id": 0,
}
if order.User != nil {
userMap = maps.Map{
"id": order.User.Id,
"fullname": order.User.Fullname,
"username": order.User.Username,
}
}
// method
var methodMap = maps.Map{
"id": 0,
}
if order.OrderMethod != nil {
methodMap = maps.Map{
"id": order.OrderMethod.Id,
"name": order.OrderMethod.Name,
}
}
this.Data["order"] = maps.Map{
"code": order.Code,
"type": order.Type,
"typeName": userconfigs.FindOrderTypeName(order.Type),
"status": order.Status,
"statusName": userconfigs.FindOrderStatusName(order.Status),
"amount": order.Amount,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", order.CreatedAt),
"cancelledTime": timeutil.FormatTime("Y-m-d H:i:s", order.CancelledAt),
"finishedTime": timeutil.FormatTime("Y-m-d H:i:s", order.FinishedAt),
"isExpired": order.IsExpired,
"user": userMap,
"method": methodMap,
}
this.Show()
}

View File

@@ -0,0 +1,37 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package orderutils
import (
"context"
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
)
// FindOrderConfig 读取订单设置
func FindOrderConfig(ctx context.Context) (*userconfigs.UserOrderConfig, error) {
rpcClient, err := rpc.SharedRPC()
if err != nil {
return nil, err
}
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(ctx, &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeUserOrderConfig})
if err != nil {
return nil, err
}
if len(resp.ValueJSON) == 0 {
return userconfigs.DefaultUserOrderConfig(), nil
}
var config = userconfigs.DefaultUserOrderConfig()
err = json.Unmarshal(resp.ValueJSON, config)
if err != nil {
return nil, err
}
return config, nil
}

View File

@@ -0,0 +1,110 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package orders
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/actions"
)
type SettingAction struct {
actionutils.ParentAction
}
func (this *SettingAction) Init() {
this.Nav("", "", "setting")
}
func (this *SettingAction) RunGet(params struct{}) {
// 配置
configResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeUserOrderConfig})
if err != nil {
this.ErrorPage(err)
return
}
var config = userconfigs.DefaultUserOrderConfig()
if len(configResp.ValueJSON) > 0 {
err = json.Unmarshal(configResp.ValueJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["config"] = config
// 当前可用支付方式数量
methodsResp, err := this.RPC().OrderMethodRPC().FindAllAvailableOrderMethods(this.AdminContext(), &pb.FindAllAvailableOrderMethodsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["hasValidMethods"] = len(methodsResp.OrderMethods) > 0
this.Show()
}
func (this *SettingAction) RunPost(params struct {
EnablePay bool
DisablePageHTML string
OrderLifeJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.Finance_LogUpdateUserOrderConfig)
// 原有配置
configResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeUserOrderConfig})
if err != nil {
this.ErrorPage(err)
return
}
var config = userconfigs.DefaultUserOrderConfig()
if len(configResp.ValueJSON) > 0 {
err = json.Unmarshal(configResp.ValueJSON, config)
if err != nil {
this.ErrorPage(err)
return
}
}
config.EnablePay = params.EnablePay
config.DisablePageHTML = params.DisablePageHTML
if len(params.OrderLifeJSON) > 0 {
var lifeDuration = &shared.TimeDuration{}
err = json.Unmarshal(params.OrderLifeJSON, lifeDuration)
if err != nil {
this.ErrorPage(err)
return
}
if lifeDuration.Count <= 0 {
this.Fail("订单过期时间必须是个有效值")
}
config.OrderLife = lifeDuration
}
configJSON, err := json.Marshal(config)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().SysSettingRPC().UpdateSysSetting(this.AdminContext(), &pb.UpdateSysSettingRequest{
Code: systemconfigs.SettingCodeUserOrderConfig,
ValueJSON: configJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,75 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package packages
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"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{}) {
this.Data["unitOptions"] = []maps.Map{
{
"code": userconfigs.TrafficPackageSizeUnitGB,
"name": "GiB",
},
{
"code": userconfigs.TrafficPackageSizeUnitTB,
"name": "TiB",
},
{
"code": userconfigs.TrafficPackageSizeUnitPB,
"name": "PiB",
},
}
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Size int32
Unit string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
var packageId int64
defer func() {
this.CreateLogInfo(codes.TrafficPackage_LogCreateTrafficPackage, packageId)
}()
if params.Size <= 0 {
this.FailField("size", "请输入正确的流量包尺寸")
return
}
// 限制最大值
if params.Unit == userconfigs.TrafficPackageSizeUnitPB && params.Size > 8000 {
this.FailField("size", "流量尺寸不能超过8000PB")
}
createResp, err := this.RPC().TrafficPackageRPC().CreateTrafficPackage(this.AdminContext(), &pb.CreateTrafficPackageRequest{
Size: params.Size,
Unit: params.Unit,
})
if err != nil {
this.ErrorPage(err)
return
}
packageId = createResp.TrafficPackageId
this.Success()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package packages
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
PackageId int64
}) {
defer this.CreateLogInfo(codes.TrafficPackage_LogDeleteTrafficPackage, params.PackageId)
_, err := this.RPC().TrafficPackageRPC().DeleteTrafficPackage(this.AdminContext(), &pb.DeleteTrafficPackageRequest{TrafficPackageId: params.PackageId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,71 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package packages
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/financeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"strings"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "index")
}
func (this *IndexAction) RunGet(params struct{}) {
// 是否已启用
config, err := financeutils.ReadPriceConfig(this.AdminContext())
if err != nil {
this.ErrorPage(err)
return
}
this.Data["enableTrafficPackages"] = config != nil && config.EnableTrafficPackages
// 流量包列表
packagesResp, err := this.RPC().TrafficPackageRPC().FindAllTrafficPackages(this.AdminContext(), &pb.FindAllTrafficPackagesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var packageMaps = []maps.Map{}
for _, p := range packagesResp.TrafficPackages {
// prices
countPricesResp, err := this.RPC().TrafficPackagePriceRPC().CountTrafficPackagePrices(this.AdminContext(), &pb.CountTrafficPackagePricesRequest{TrafficPackageId: p.Id})
if err != nil {
this.ErrorPage(err)
return
}
packageMaps = append(packageMaps, maps.Map{
"id": p.Id,
"size": p.Size,
"unit": strings.ToUpper(p.Unit),
"isOn": p.IsOn,
"countPrices": countPricesResp.Count,
})
}
this.Data["packages"] = packageMaps
// 所有区域
regionsResp, err := this.RPC().NodeRegionRPC().FindAllAvailableNodeRegions(this.AdminContext(), &pb.FindAllAvailableNodeRegionsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
// 有效期
periodsResp, err := this.RPC().TrafficPackagePeriodRPC().FindAllAvailableTrafficPackagePeriods(this.AdminContext(), &pb.FindAllAvailableTrafficPackagePeriodsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["totalPriceItems"] = len(regionsResp.NodeRegions) * len(periodsResp.TrafficPackagePeriods)
this.Show()
}

View File

@@ -0,0 +1,52 @@
//go:build plus
package packages
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/packages/periods"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/packages/periods/period"
userpackages "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/packages/user-packages"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(plus.NewHelper(plus.ComponentCodeFinance)).
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeFinance)).
Data("teaMenu", "finance").
Data("teaSubMenu", "package").
// 流量包
Prefix("/finance/packages").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/updatePopup", new(UpdatePopupAction)).
Post("/delete", new(DeleteAction)).
GetPost("/updatePricesPopup", new(UpdatePricesPopupAction)).
Post("/updatePrice", new(UpdatePriceAction)).
// 流量包有效期
Prefix("/finance/packages/periods").
Get("", new(periods.IndexAction)).
GetPost("/createPopup", new(periods.CreatePopupAction)).
// 流量包有效期详情
Prefix("/finance/packages/periods/period").
GetPost("/updatePopup", new(period.UpdatePopupAction)).
Post("/delete", new(period.DeleteAction)).
// 用户流量包
Prefix("/finance/packages/user-packages").
Get("", new(userpackages.IndexAction)).
GetPost("/createPopup", new(userpackages.CreatePopupAction)).
Post("/price", new(userpackages.PriceAction)).
Post("/delete", new(userpackages.DeleteAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,52 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package periods
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"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 {
Count int32
Unit string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
var periodId int64
defer func() {
this.CreateLogInfo(codes.TrafficPackagePeriod_LogCreateTrafficPackagePeriod, periodId)
}()
if params.Count <= 0 {
this.FailField("count", "请输入有效期数量")
}
createResp, err := this.RPC().TrafficPackagePeriodRPC().CreateTrafficPackagePeriod(this.AdminContext(), &pb.CreateTrafficPackagePeriodRequest{
Count: params.Count,
Unit: params.Unit,
})
if err != nil {
this.ErrorPage(err)
return
}
periodId = createResp.TrafficPackagePeriodId
this.Success()
}

View File

@@ -0,0 +1,49 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package periods
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/finance/financeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "period")
}
func (this *IndexAction) RunGet(params struct{}) {
// 是否已启用
config, err := financeutils.ReadPriceConfig(this.AdminContext())
if err != nil {
this.ErrorPage(err)
return
}
this.Data["enableTrafficPackages"] = config != nil && config.EnableTrafficPackages
// 所有有效期
periodsResp, err := this.RPC().TrafficPackagePeriodRPC().FindAllTrafficPackagePeriods(this.AdminContext(), &pb.FindAllTrafficPackagePeriodsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var periodMaps = []maps.Map{}
for _, period := range periodsResp.TrafficPackagePeriods {
periodMaps = append(periodMaps, maps.Map{
"id": period.Id,
"count": period.Count,
"unit": period.Unit,
"unitName": userconfigs.TrafficPackagePeriodUnitName(period.Unit),
"isOn": period.IsOn,
})
}
this.Data["periods"] = periodMaps
this.Show()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package period
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
PeriodId int64
}) {
defer this.CreateLogInfo(codes.TrafficPackagePeriod_LogDeleteTrafficPackagePeriod, params.PeriodId)
_, err := this.RPC().TrafficPackagePeriodRPC().DeleteTrafficPackagePeriod(this.AdminContext(), &pb.DeleteTrafficPackagePeriodRequest{TrafficPackagePeriodId: params.PeriodId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,64 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package period
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"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 {
PeriodId int64
}) {
periodResp, err := this.RPC().TrafficPackagePeriodRPC().FindTrafficPackagePeriod(this.AdminContext(), &pb.FindTrafficPackagePeriodRequest{TrafficPackagePeriodId: params.PeriodId})
if err != nil {
this.ErrorPage(err)
return
}
var period = periodResp.TrafficPackagePeriod
if period == nil {
this.NotFound("trafficPackagePeriod", params.PeriodId)
return
}
this.Data["period"] = maps.Map{
"id": period.Id,
"count": period.Count,
"unitName": userconfigs.PricePeriodName(period.Unit),
"isOn": period.IsOn,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
PeriodId int64
IsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.TrafficPackagePeriod_LogUpdateTrafficPackagePeriod, params.PeriodId)
_, err := this.RPC().TrafficPackagePeriodRPC().UpdateTrafficPackagePeriod(this.AdminContext(), &pb.UpdateTrafficPackagePeriodRequest{
TrafficPackagePeriodId: params.PeriodId,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,65 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package packages
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"strings"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
PackageId int64
}) {
packageResp, err := this.RPC().TrafficPackageRPC().FindTrafficPackage(this.AdminContext(), &pb.FindTrafficPackageRequest{TrafficPackageId: params.PackageId})
if err != nil {
this.ErrorPage(err)
return
}
var p = packageResp.TrafficPackage
if p == nil {
this.NotFound("trafficPackage", params.PackageId)
return
}
this.Data["package"] = maps.Map{
"id": p.Id,
"size": p.Size,
"unit": strings.ToUpper(p.Unit),
"isOn": p.IsOn,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
PackageId int64
IsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.TrafficPackage_LogUpdateTrafficPackage, params.PackageId)
_, err := this.RPC().TrafficPackageRPC().UpdateTrafficPackage(this.AdminContext(), &pb.UpdateTrafficPackageRequest{
TrafficPackageId: params.PackageId,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,35 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package packages
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type UpdatePriceAction struct {
actionutils.ParentAction
}
func (this *UpdatePriceAction) RunPost(params struct {
PackageId int64
RegionId int64
PeriodId int64
Price float64
}) {
defer this.CreateLogInfo(codes.TrafficPackagePrice_LogUpdateTrafficPackagePrice, params.PackageId, params.RegionId, params.PeriodId)
_, err := this.RPC().TrafficPackagePriceRPC().UpdateTrafficPackagePrice(this.AdminContext(), &pb.UpdateTrafficPackagePriceRequest{
TrafficPackageId: params.PackageId,
NodeRegionId: params.RegionId,
TrafficPackagePeriodId: params.PeriodId,
Price: params.Price,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,88 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package packages
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"strings"
)
type UpdatePricesPopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePricesPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePricesPopupAction) RunGet(params struct {
PackageId int64
}) {
packageResp, err := this.RPC().TrafficPackageRPC().FindTrafficPackage(this.AdminContext(), &pb.FindTrafficPackageRequest{
TrafficPackageId: params.PackageId,
})
if err != nil {
this.ErrorPage(err)
return
}
var p = packageResp.TrafficPackage
if p == nil {
this.NotFound("trafficPackage", params.PackageId)
return
}
this.Data["package"] = maps.Map{
"id": p.Id,
"size": p.Size,
"unit": strings.ToUpper(p.Unit),
}
// 所有区域
regionsResp, err := this.RPC().NodeRegionRPC().FindAllAvailableNodeRegions(this.AdminContext(), &pb.FindAllAvailableNodeRegionsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var regionMaps = []maps.Map{}
for _, region := range regionsResp.NodeRegions {
regionMaps = append(regionMaps, maps.Map{
"id": region.Id,
"name": region.Name,
})
}
this.Data["regions"] = regionMaps
// 有效期
periodsResp, err := this.RPC().TrafficPackagePeriodRPC().FindAllAvailableTrafficPackagePeriods(this.AdminContext(), &pb.FindAllAvailableTrafficPackagePeriodsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var periodMaps = []maps.Map{}
for _, period := range periodsResp.TrafficPackagePeriods {
periodMaps = append(periodMaps, maps.Map{
"id": period.Id,
"name": types.String(period.Count) + userconfigs.TrafficPackagePeriodUnitName(period.Unit),
})
}
this.Data["periods"] = periodMaps
// 所有价格
pricesResp, err := this.RPC().TrafficPackagePriceRPC().FindTrafficPackagePrices(this.AdminContext(), &pb.FindTrafficPackagePricesRequest{TrafficPackageId: params.PackageId})
if err != nil {
this.ErrorPage(err)
return
}
var priceMap = map[string]float64{} // regionId@periodId => price
for _, price := range pricesResp.TrafficPackagePrices {
priceMap[types.String(price.NodeRegionId)+"@"+types.String(price.TrafficPackagePeriodId)] = price.Price
}
this.Data["prices"] = priceMap
this.Show()
}

View File

@@ -0,0 +1,171 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package userpackages
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
// 价格
pricesResp, err := this.RPC().TrafficPackagePriceRPC().FindAllTrafficPackagePrices(this.AdminContext(), &pb.FindAllTrafficPackagePricesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var priceMap = map[string]float64{} // packageId@regionId@periodId => price
var allPackageIdMap = map[int64]bool{}
var allRegionIdMap = map[int64]bool{}
var allPeriodIdMap = map[int64]bool{}
for _, price := range pricesResp.TrafficPackagePrices {
if price.Price > 0 {
allPackageIdMap[price.TrafficPackageId] = true
allRegionIdMap[price.NodeRegionId] = true
allPeriodIdMap[price.TrafficPackagePeriodId] = true
var key = types.String(price.TrafficPackageId) + "@" + types.String(price.NodeRegionId) + "@" + types.String(price.TrafficPackagePeriodId)
priceMap[key] = price.Price
}
}
this.Data["prices"] = priceMap
// 流量包
var packageMaps = []maps.Map{}
packagesResp, err := this.RPC().TrafficPackageRPC().FindAllAvailableTrafficPackages(this.AdminContext(), &pb.FindAllAvailableTrafficPackagesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
for _, p := range packagesResp.TrafficPackages {
_, hasPrice := allPackageIdMap[p.Id]
if hasPrice {
packageMaps = append(packageMaps, maps.Map{
"id": p.Id,
"size": p.Size,
"unit": p.Unit,
})
}
}
this.Data["packages"] = packageMaps
// 区域
var regionMaps = []maps.Map{}
regionsResp, err := this.RPC().NodeRegionRPC().FindAllAvailableNodeRegions(this.AdminContext(), &pb.FindAllAvailableNodeRegionsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
for _, region := range regionsResp.NodeRegions {
_, hasPrice := allRegionIdMap[region.Id]
if hasPrice {
regionMaps = append(regionMaps, maps.Map{
"id": region.Id,
"name": region.Name,
})
}
}
this.Data["regions"] = regionMaps
// 周期
var periodMaps = []maps.Map{}
periodResp, err := this.RPC().TrafficPackagePeriodRPC().FindAllAvailableTrafficPackagePeriods(this.AdminContext(), &pb.FindAllAvailableTrafficPackagePeriodsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
for _, period := range periodResp.TrafficPackagePeriods {
_, hasPrice := allPeriodIdMap[period.Id]
if hasPrice {
periodMaps = append(periodMaps, maps.Map{
"id": period.Id,
"count": period.Count,
"unit": period.Unit,
"unitName": userconfigs.TrafficPackagePeriodUnitName(period.Unit),
})
}
}
this.Data["periods"] = periodMaps
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
UserId int64
PackageId int64
RegionId int64
PeriodId int64
Count int32
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.UserTrafficPackage_LogCreateUserTrafficPackage, params.UserId, params.PackageId, params.RegionId, params.PeriodId, params.Count)
if params.UserId <= 0 {
this.Fail("请选择用户")
return
}
if params.PackageId <= 0 {
this.Fail("请选择流量包规格")
return
}
if params.RegionId <= 0 {
this.Fail("请选择区域")
return
}
if params.PeriodId <= 0 {
this.Fail("请选择有效期")
return
}
if params.Count <= 0 {
this.Fail("请选择流量包数量")
return
}
resp, err := this.RPC().TrafficPackagePriceRPC().FindTrafficPackagePrice(this.AdminContext(), &pb.FindTrafficPackagePriceRequest{
TrafficPackageId: params.PackageId,
NodeRegionId: params.RegionId,
TrafficPackagePeriodId: params.PeriodId,
Count: params.Count,
})
if err != nil {
this.ErrorPage(err)
return
}
if resp.Price == 0 {
this.Fail("当前所选条件下的流量包不可用")
return
}
_, err = this.RPC().UserTrafficPackageRPC().CreateUserTrafficPackage(this.AdminContext(), &pb.CreateUserTrafficPackageRequest{
UserId: params.UserId,
TrafficPackageId: params.PackageId,
NodeRegionId: params.RegionId,
TrafficPackagePeriodId: params.PeriodId,
Count: params.Count,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package userpackages
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
UserPackageId int64
}) {
defer this.CreateLogInfo(codes.UserTrafficPackage_LogDeleteUserTrafficPackage, params.UserPackageId)
_, err := this.RPC().UserTrafficPackageRPC().DeleteUserTrafficPackage(this.AdminContext(), &pb.DeleteUserTrafficPackageRequest{UserTrafficPackageId: params.UserPackageId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,110 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package userpackages
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/dateutils"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "userPackage")
}
func (this *IndexAction) RunGet(params struct {
UserId int64
RegionId int64
PackageId int64
PeriodId int64
}) {
countResp, err := this.RPC().UserTrafficPackageRPC().CountUserTrafficPackages(this.AdminContext(), &pb.CountUserTrafficPackagesRequest{
TrafficPackageId: params.PackageId,
NodeRegionId: params.RegionId,
UserId: params.UserId,
ExpiresDay: "",
TrafficPackagePeriodId: params.PeriodId,
})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
var page = this.NewPage(count)
this.Data["page"] = page.AsHTML()
userPackagesResp, err := this.RPC().UserTrafficPackageRPC().ListUserTrafficPackages(this.AdminContext(), &pb.ListUserTrafficPackagesRequest{
TrafficPackageId: params.PackageId,
UserId: params.UserId,
NodeRegionId: params.RegionId,
TrafficPackagePeriodId: params.PeriodId,
ExpiresDay: "",
AvailableOnly: false,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var userPackageMaps = []maps.Map{}
for _, userPackage := range userPackagesResp.UserTrafficPackages {
// user
var userMap maps.Map
if userPackage.User != nil {
userMap = maps.Map{
"id": userPackage.User.Id,
"username": userPackage.User.Username,
"fullname": userPackage.User.Fullname,
}
}
// package
var packageMap maps.Map
if userPackage.TrafficPackage != nil {
packageMap = maps.Map{
"id": userPackage.TrafficPackage.Id,
"size": userPackage.TrafficPackage.Size,
"unit": userPackage.TrafficPackage.Unit,
}
}
// region
var regionMap maps.Map
if userPackage.NodeRegion != nil {
regionMap = maps.Map{
"id": userPackage.NodeRegion.Id,
"name": userPackage.NodeRegion.Name,
}
}
userPackageMaps = append(userPackageMaps, maps.Map{
"id": userPackage.Id,
"dayFrom": dateutils.SplitYmd(userPackage.DayFrom),
"dayTo": dateutils.SplitYmd(userPackage.DayTo),
"user": userMap,
"package": packageMap,
"region": regionMap,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", userPackage.CreatedAt),
"periodCount": userPackage.TrafficPackagePeriodCount,
"periodUnitName": userconfigs.TrafficPackagePeriodUnitName(userPackage.TrafficPackagePeriodUnit),
"usedSize": numberutils.FormatBytes(userPackage.UsedBytes),
"availableSize": numberutils.FormatBytes(userPackage.TotalBytes - userPackage.UsedBytes),
"canDelete": userPackage.CanDelete,
"isExpired": userPackage.DayTo < timeutil.Format("Ymd"),
"isUsedAll": userPackage.UsedBytes >= userPackage.TotalBytes,
})
}
this.Data["userPackages"] = userPackageMaps
this.Show()
}

View File

@@ -0,0 +1,41 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package userpackages
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type PriceAction struct {
actionutils.ParentAction
}
func (this *PriceAction) RunPost(params struct {
PackageId int64
RegionId int64
PeriodId int64
Count int32
}) {
if params.PackageId <= 0 || params.RegionId <= 0 || params.PeriodId <= 0 || params.Count <= 0 {
this.Data["price"] = 0
this.Data["amount"] = 0
this.Success()
return
}
resp, err := this.RPC().TrafficPackagePriceRPC().FindTrafficPackagePrice(this.AdminContext(), &pb.FindTrafficPackagePriceRequest{
TrafficPackageId: params.PackageId,
NodeRegionId: params.RegionId,
TrafficPackagePeriodId: params.PeriodId,
Count: params.Count,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["price"] = resp.Price
this.Data["amount"] = resp.Amount
this.Success()
}

View File

@@ -0,0 +1,38 @@
package users
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type OptionsAction struct {
actionutils.ParentAction
}
func (this *OptionsAction) RunPost(params struct {
Keyword string
}) {
usersResp, err := this.RPC().UserRPC().ListEnabledUsers(this.AdminContext(), &pb.ListEnabledUsersRequest{
Keyword: params.Keyword,
Offset: 0,
Size: 100,
})
if err != nil {
this.ErrorPage(err)
return
}
var userMaps = []maps.Map{}
for _, user := range usersResp.Users {
userMaps = append(userMaps, maps.Map{
"id": user.Id,
"fullname": user.Fullname,
"username": user.Username,
"name": user.Fullname + "(" + user.Username + ")",
})
}
this.Data["users"] = userMaps
this.Success()
}