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,12 @@
package about
import "github.com/iwind/TeaGo"
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Prefix("/about").
Get("/qq", new(QqAction)).
EndAll()
})
}

View File

@@ -0,0 +1,15 @@
package about
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type QqAction struct {
actionutils.ParentAction
}
func (this *QqAction) Init() {
this.Nav("", "", "")
}
func (this *QqAction) RunGet(params struct{}) {
this.Show()
}

View File

@@ -0,0 +1,50 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accesskeys
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 {
AdminId int64
}) {
this.Data["adminId"] = params.AdminId
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
AdminId int64
Description string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("description", params.Description).
Require("请输入备注")
accessKeyIdResp, err := this.RPC().UserAccessKeyRPC().CreateUserAccessKey(this.AdminContext(), &pb.CreateUserAccessKeyRequest{
AdminId: params.AdminId,
Description: params.Description,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo(codes.UserAccessKey_LogCreateUserAccessKey, accessKeyIdResp.UserAccessKeyId)
this.Success()
}

View File

@@ -0,0 +1,25 @@
package accesskeys
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 {
AccessKeyId int64
}) {
defer this.CreateLogInfo(codes.UserAccessKey_LogDeleteUserAccessKey, params.AccessKeyId)
_, err := this.RPC().UserAccessKeyRPC().DeleteUserAccessKey(this.AdminContext(), &pb.DeleteUserAccessKeyRequest{UserAccessKeyId: params.AccessKeyId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,54 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accesskeys
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/admins/adminutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "accessKey")
}
func (this *IndexAction) RunGet(params struct {
AdminId int64
}) {
err := adminutils.InitAdmin(this.Parent(), params.AdminId)
if err != nil {
this.ErrorPage(err)
return
}
accessKeysResp, err := this.RPC().UserAccessKeyRPC().FindAllEnabledUserAccessKeys(this.AdminContext(), &pb.FindAllEnabledUserAccessKeysRequest{AdminId: params.AdminId})
if err != nil {
this.ErrorPage(err)
return
}
accessKeyMaps := []maps.Map{}
for _, accessKey := range accessKeysResp.UserAccessKeys {
var accessedTime string
if accessKey.AccessedAt > 0 {
accessedTime = timeutil.FormatTime("Y-m-d H:i:s", accessKey.AccessedAt)
}
accessKeyMaps = append(accessKeyMaps, maps.Map{
"id": accessKey.Id,
"isOn": accessKey.IsOn,
"uniqueId": accessKey.UniqueId,
"secret": accessKey.Secret,
"description": accessKey.Description,
"accessedTime": accessedTime,
})
}
this.Data["accessKeys"] = accessKeyMaps
this.Show()
}

View File

@@ -0,0 +1,29 @@
package accesskeys
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type UpdateIsOnAction struct {
actionutils.ParentAction
}
func (this *UpdateIsOnAction) RunPost(params struct {
AccessKeyId int64
IsOn bool
}) {
defer this.CreateLogInfo(codes.UserAccessKey_LogUpdateUserAccessKeyIsOn, params.AccessKeyId)
_, err := this.RPC().UserAccessKeyRPC().UpdateUserAccessKeyIsOn(this.AdminContext(), &pb.UpdateUserAccessKeyIsOnRequest{
UserAccessKeyId: params.AccessKeyId,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,85 @@
package admins
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type AdminAction struct {
actionutils.ParentAction
}
func (this *AdminAction) Init() {
this.Nav("", "", "index")
}
func (this *AdminAction) RunGet(params struct {
AdminId int64
}) {
adminResp, err := this.RPC().AdminRPC().FindEnabledAdmin(this.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: params.AdminId})
if err != nil {
this.ErrorPage(err)
return
}
admin := adminResp.Admin
if admin == nil {
this.NotFound("admin", params.AdminId)
return
}
// AccessKey数量
countAccessKeyResp, err := this.RPC().UserAccessKeyRPC().CountAllEnabledUserAccessKeys(this.AdminContext(), &pb.CountAllEnabledUserAccessKeysRequest{AdminId: params.AdminId})
if err != nil {
this.ErrorPage(err)
return
}
countAccessKeys := countAccessKeyResp.Count
this.Data["admin"] = maps.Map{
"id": admin.Id,
"fullname": admin.Fullname,
"username": admin.Username,
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"canLogin": admin.CanLogin,
"hasWeakPassword": admin.HasWeakPassword,
"countAccessKeys": countAccessKeys,
}
// 权限
moduleMaps := []maps.Map{}
for _, m := range configloaders.AllModuleMaps(this.LangCode()) {
code := m.GetString("code")
isChecked := false
for _, module := range admin.Modules {
if module.Code == code {
isChecked = true
break
}
}
if isChecked {
moduleMaps = append(moduleMaps, m)
}
}
this.Data["modules"] = moduleMaps
// OTP
this.Data["otp"] = nil
if admin.OtpLogin != nil && admin.OtpLogin.IsOn {
loginParams := maps.Map{}
err = json.Unmarshal(admin.OtpLogin.ParamsJSON, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["otp"] = maps.Map{
"isOn": true,
"params": loginParams,
}
}
this.Show()
}

View File

@@ -0,0 +1,38 @@
package adminutils
import (
"errors"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
// InitAdmin 查找管理员基本信息
func InitAdmin(p *actionutils.ParentAction, adminId int64) error {
// 管理员信息
resp, err := p.RPC().AdminRPC().FindEnabledAdmin(p.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: adminId})
if err != nil {
return err
}
if resp.Admin == nil {
return errors.New("not found admin")
}
var admin = resp.Admin
// AccessKey数量
countAccessKeysResp, err := p.RPC().UserAccessKeyRPC().CountAllEnabledUserAccessKeys(p.AdminContext(), &pb.CountAllEnabledUserAccessKeysRequest{
AdminId: adminId,
UserId: 0,
})
if err != nil {
return err
}
p.Data["admin"] = maps.Map{
"id": admin.Id,
"username": admin.Username,
"fullname": admin.Fullname,
"countAccessKeys": countAccessKeysResp.Count,
}
return nil
}

View File

@@ -0,0 +1,128 @@
package admins
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"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/systemconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/xlzd/gotp"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Data["modules"] = configloaders.AllModuleMaps(this.LangCode())
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Fullname string
Username string
Pass1 string
Pass2 string
ModuleCodes []string
IsSuper bool
CanLogin bool
// OTP
OtpOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("fullname", params.Fullname).
Require("请输入系统用户全名")
params.Must.
Field("username", params.Username).
Require("请输入登录用户名").
Match(`^[a-zA-Z0-9_]+$`, "用户名中只能包含英文、数字或下划线")
existsResp, err := this.RPC().AdminRPC().CheckAdminUsername(this.AdminContext(), &pb.CheckAdminUsernameRequest{
AdminId: 0,
Username: params.Username,
})
if err != nil {
this.ErrorPage(err)
return
}
if existsResp.Exists {
this.FailField("username", "此用户名已经被别的系统用户使用,请换一个")
}
params.Must.
Field("pass1", params.Pass1).
Require("请输入登录密码").
Field("pass2", params.Pass2).
Require("请输入确认登录密码")
if params.Pass1 != params.Pass2 {
this.FailField("pass2", "两次输入的密码不一致")
}
modules := []*systemconfigs.AdminModule{}
for _, code := range params.ModuleCodes {
modules = append(modules, &systemconfigs.AdminModule{
Code: code,
AllowAll: true,
Actions: nil, // TODO 后期再开放细粒度控制
})
}
modulesJSON, err := json.Marshal(modules)
if err != nil {
this.ErrorPage(err)
return
}
createResp, err := this.RPC().AdminRPC().CreateAdmin(this.AdminContext(), &pb.CreateAdminRequest{
Username: params.Username,
Password: params.Pass1,
Fullname: params.Fullname,
ModulesJSON: modulesJSON,
IsSuper: params.IsSuper,
CanLogin: params.CanLogin,
})
if err != nil {
this.ErrorPage(err)
return
}
// OTP
if params.OtpOn {
_, err = this.RPC().LoginRPC().UpdateLogin(this.AdminContext(), &pb.UpdateLoginRequest{Login: &pb.Login{
Id: 0,
Type: "otp",
ParamsJSON: maps.Map{
"secret": gotp.RandomSecret(16), // TODO 改成可以设置secret长度
}.AsJSON(),
IsOn: true,
AdminId: createResp.AdminId,
UserId: 0,
}})
if err != nil {
this.ErrorPage(err)
return
}
}
defer this.CreateLogInfo(codes.Admin_LogCreateAdmin, createResp.AdminId)
// 通知更改
err = configloaders.NotifyAdminModuleMappingChange()
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,33 @@
package admins
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"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 {
AdminId int64
}) {
defer this.CreateLogInfo(codes.Admin_LogDeleteAdmin, params.AdminId)
_, err := this.RPC().AdminRPC().DeleteAdmin(this.AdminContext(), &pb.DeleteAdminRequest{AdminId: params.AdminId})
if err != nil {
this.ErrorPage(err)
return
}
// 通知更改
err = configloaders.NotifyAdminModuleMappingChange()
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,63 @@
package admins
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 IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct {
Keyword string
HasWeakPassword bool
}) {
this.Data["keyword"] = params.Keyword
this.Data["hasWeakPassword"] = params.HasWeakPassword
countResp, err := this.RPC().AdminRPC().CountAllEnabledAdmins(this.AdminContext(), &pb.CountAllEnabledAdminsRequest{
Keyword: params.Keyword,
HasWeakPassword: params.HasWeakPassword,
})
if err != nil {
this.ErrorPage(err)
return
}
var page = this.NewPage(countResp.Count)
this.Data["page"] = page.AsHTML()
adminsResp, err := this.RPC().AdminRPC().ListEnabledAdmins(this.AdminContext(), &pb.ListEnabledAdminsRequest{
Keyword: params.Keyword,
HasWeakPassword: params.HasWeakPassword,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var adminMaps = []maps.Map{}
for _, admin := range adminsResp.Admins {
adminMaps = append(adminMaps, maps.Map{
"id": admin.Id,
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"username": admin.Username,
"fullname": admin.Fullname,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", admin.CreatedAt),
"otpLoginIsOn": admin.OtpLogin != nil && admin.OtpLogin.IsOn,
"canLogin": admin.CanLogin,
"hasWeakPassword": admin.HasWeakPassword,
})
}
this.Data["admins"] = adminMaps
this.Show()
}

View File

@@ -0,0 +1,32 @@
package admins
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/admins/accesskeys"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Prefix("/admins").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/update", new(UpdateAction)).
Post("/delete", new(DeleteAction)).
Get("/admin", new(AdminAction)).
Get("/otpQrcode", new(OtpQrcodeAction)).
Post("/options", new(OptionsAction)).
// AccessKeys
Prefix("/admins/accesskeys").
Get("", new(accesskeys.IndexAction)).
GetPost("/createPopup", new(accesskeys.CreatePopupAction)).
Post("/delete", new(accesskeys.DeleteAction)).
Post("/updateIsOn", new(accesskeys.UpdateIsOnAction)).
EndAll()
})
}

View File

@@ -0,0 +1,40 @@
package admins
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) Init() {
this.Nav("", "", "")
}
func (this *OptionsAction) RunPost(params struct{}) {
// TODO 实现关键词搜索
adminsResp, err := this.RPC().AdminRPC().ListEnabledAdmins(this.AdminContext(), &pb.ListEnabledAdminsRequest{
Offset: 0,
Size: 1000,
})
if err != nil {
this.ErrorPage(err)
return
}
adminMaps := []maps.Map{}
for _, admin := range adminsResp.Admins {
adminMaps = append(adminMaps, maps.Map{
"id": admin.Id,
"name": admin.Fullname,
"username": admin.Username,
})
}
this.Data["admins"] = adminMaps
this.Success()
}

View File

@@ -0,0 +1,81 @@
package admins
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/otputils"
"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"
"github.com/skip2/go-qrcode"
"github.com/xlzd/gotp"
)
type OtpQrcodeAction struct {
actionutils.ParentAction
}
func (this *OtpQrcodeAction) Init() {
this.Nav("", "", "")
}
func (this *OtpQrcodeAction) RunGet(params struct {
AdminId int64
Download bool
}) {
loginResp, err := this.RPC().LoginRPC().FindEnabledLogin(this.AdminContext(), &pb.FindEnabledLoginRequest{
AdminId: params.AdminId,
Type: "otp",
})
if err != nil {
this.ErrorPage(err)
return
}
var login = loginResp.Login
if login == nil || !login.IsOn {
this.NotFound("adminLogin", params.AdminId)
return
}
var loginParams = maps.Map{}
err = json.Unmarshal(login.ParamsJSON, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
var secret = loginParams.GetString("secret")
// 当前用户信息
adminResp, err := this.RPC().AdminRPC().FindEnabledAdmin(this.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: params.AdminId})
if err != nil {
this.ErrorPage(err)
return
}
var admin = adminResp.Admin
if admin == nil {
this.NotFound("admin", params.AdminId)
return
}
uiConfig, err := configloaders.LoadAdminUIConfig()
if err != nil {
this.ErrorPage(err)
return
}
var url = gotp.NewDefaultTOTP(secret).ProvisioningUri(admin.Username, uiConfig.AdminSystemName)
data, err := qrcode.Encode(otputils.FixIssuer(url), qrcode.Medium, 256)
if err != nil {
this.ErrorPage(err)
return
}
if params.Download {
var filename = "OTP-ADMIN-" + admin.Username + ".png"
this.AddHeader("Content-Disposition", "attachment; filename=\""+filename+"\";")
}
this.AddHeader("Content-Type", "image/png")
this.AddHeader("Content-Length", types.String(len(data)))
_, _ = this.Write(data)
}

View File

@@ -0,0 +1,101 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"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"
"regexp"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
// node clusters
nodeClustersResp, err := this.RPC().NodeClusterRPC().FindAllEnabledNodeClusters(this.AdminContext(), &pb.FindAllEnabledNodeClustersRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var nodeClusterMaps = []maps.Map{}
for _, nodeCluster := range nodeClustersResp.NodeClusters {
nodeClusterMaps = append(nodeClusterMaps, maps.Map{
"id": nodeCluster.Id,
"name": nodeCluster.Name,
})
}
this.Data["nodeClusters"] = nodeClusterMaps
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
AdminId int64
InstanceId int64
User string
TelegramToken string
GroupIds string
Description string
TimeFromHour string
TimeFromMinute string
TimeFromSecond string
TimeToHour string
TimeToMinute string
TimeToSecond string
NodeClusterIds []int64
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("adminId", params.AdminId).
Gt(0, "请选择系统用户").
Field("instanceId", params.InstanceId).
Gt(0, "请选择媒介")
var groupIds = utils.SplitNumbers(params.GroupIds)
var digitReg = regexp.MustCompile(`^\d+$`)
var timeFrom = ""
if digitReg.MatchString(params.TimeFromHour) && digitReg.MatchString(params.TimeFromMinute) && digitReg.MatchString(params.TimeFromSecond) {
timeFrom = params.TimeFromHour + ":" + params.TimeFromMinute + ":" + params.TimeFromSecond
}
var timeTo = ""
if digitReg.MatchString(params.TimeToHour) && digitReg.MatchString(params.TimeToMinute) && digitReg.MatchString(params.TimeToSecond) {
timeTo = params.TimeToHour + ":" + params.TimeToMinute + ":" + params.TimeToSecond
}
resp, err := this.RPC().MessageRecipientRPC().CreateMessageRecipient(this.AdminContext(), &pb.CreateMessageRecipientRequest{
AdminId: params.AdminId,
MessageMediaInstanceId: params.InstanceId,
User: params.User,
MessageRecipientGroupIds: groupIds,
Description: params.Description,
TimeFrom: timeFrom,
TimeTo: timeTo,
NodeClusterIds: params.NodeClusterIds,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo(codes.MessageRecipient_LogCreateMessageRecipient, resp.MessageRecipientId)
this.Success()
}

View File

@@ -0,0 +1,25 @@
package recipients
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 {
RecipientId int64
}) {
defer this.CreateLogInfo(codes.MessageRecipient_LogDeleteMessageRecipient, params.RecipientId)
_, err := this.RPC().MessageRecipientRPC().DeleteMessageRecipient(this.AdminContext(), &pb.DeleteMessageRecipientRequest{MessageRecipientId: params.RecipientId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,38 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入分组名称")
_, err := this.RPC().MessageRecipientGroupRPC().CreateMessageRecipientGroup(this.AdminContext(), &pb.CreateMessageRecipientGroupRequest{Name: params.Name})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,22 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
GroupId int64
}) {
_, err := this.RPC().MessageRecipientGroupRPC().DeleteMessageRecipientGroup(this.AdminContext(), &pb.DeleteMessageRecipientGroupRequest{MessageRecipientGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,34 @@
package groups
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("", "", "group")
}
func (this *IndexAction) RunGet(params struct{}) {
groupsResp, err := this.RPC().MessageRecipientGroupRPC().FindAllEnabledMessageRecipientGroups(this.AdminContext(), &pb.FindAllEnabledMessageRecipientGroupsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
groupMaps := []maps.Map{}
for _, group := range groupsResp.MessageRecipientGroups {
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": group.Name,
"isOn": group.IsOn,
})
}
this.Data["groups"] = groupMaps
this.Show()
}

View File

@@ -0,0 +1,23 @@
package groups
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.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Data("teaSubMenu", "recipients").
Prefix("/admins/recipients/groups").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/updatePopup", new(UpdatePopupAction)).
Post("/delete", new(DeleteAction)).
Get("/selectPopup", new(SelectPopupAction)).
EndAll()
})
}

View File

@@ -0,0 +1,39 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
)
type SelectPopupAction struct {
actionutils.ParentAction
}
func (this *SelectPopupAction) RunGet(params struct {
GroupIds string
}) {
selectedGroupIds := utils.SplitNumbers(params.GroupIds)
groupsResp, err := this.RPC().MessageRecipientGroupRPC().FindAllEnabledMessageRecipientGroups(this.AdminContext(), &pb.FindAllEnabledMessageRecipientGroupsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
groupMaps := []maps.Map{}
for _, group := range groupsResp.MessageRecipientGroups {
if lists.ContainsInt64(selectedGroupIds, group.Id) {
continue
}
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": group.Name,
"isOn": group.IsOn,
})
}
this.Data["groups"] = groupMaps
this.Show()
}

View File

@@ -0,0 +1,64 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
GroupId int64
}) {
groupResp, err := this.RPC().MessageRecipientGroupRPC().FindEnabledMessageRecipientGroup(this.AdminContext(), &pb.FindEnabledMessageRecipientGroupRequest{MessageRecipientGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
group := groupResp.MessageRecipientGroup
if group == nil {
this.NotFound("messageRecipientGroup", params.GroupId)
return
}
this.Data["group"] = maps.Map{
"id": group.Id,
"name": group.Name,
"isOn": group.IsOn,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
GroupId int64
Name string
IsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入分组名称")
_, err := this.RPC().MessageRecipientGroupRPC().UpdateMessageRecipientGroup(this.AdminContext(), &pb.UpdateMessageRecipientGroupRequest{
MessageRecipientGroupId: params.GroupId,
Name: params.Name,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,74 @@
package recipients
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("", "", "recipient")
}
func (this *IndexAction) RunGet(params struct {
}) {
// TODO 增加系统用户、媒介类型等条件搜索
countResp, err := this.RPC().MessageRecipientRPC().CountAllEnabledMessageRecipients(this.AdminContext(), &pb.CountAllEnabledMessageRecipientsRequest{
AdminId: 0,
MediaType: "",
MessageRecipientGroupId: 0,
Keyword: "",
})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
recipientsResp, err := this.RPC().MessageRecipientRPC().ListEnabledMessageRecipients(this.AdminContext(), &pb.ListEnabledMessageRecipientsRequest{
AdminId: 0,
MediaType: "",
MessageRecipientGroupId: 0,
Keyword: "",
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
recipientMaps := []maps.Map{}
for _, recipient := range recipientsResp.MessageRecipients {
if recipient.Admin == nil {
continue
}
if recipient.MessageMediaInstance == nil {
continue
}
recipientMaps = append(recipientMaps, maps.Map{
"id": recipient.Id,
"admin": maps.Map{
"id": recipient.Admin.Id,
"fullname": recipient.Admin.Fullname,
"username": recipient.Admin.Username,
},
"groups": recipient.MessageRecipientGroups,
"isOn": recipient.IsOn,
"instance": maps.Map{
"name": recipient.MessageMediaInstance.Name,
},
"user": recipient.User,
"description": recipient.Description,
})
}
this.Data["recipients"] = recipientMaps
this.Show()
}

View File

@@ -0,0 +1,25 @@
package recipients
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.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Data("teaSubMenu", "recipients").
Prefix("/admins/recipients").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/update", new(UpdateAction)).
Post("/delete", new(DeleteAction)).
Post("/mediaOptions", new(MediaOptionsAction)).
Get("/recipient", new(RecipientAction)).
GetPost("/test", new(TestAction)).
EndAll()
})
}

View File

@@ -0,0 +1,297 @@
package instances
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/monitorconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"net/url"
"regexp"
"strings"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
MediaType string
EmailSmtp string
EmailUsername string
EmailPassword string
EmailFrom string
WebHookURL string
WebHookMethod string
WebHookHeaderNames []string
WebHookHeaderValues []string
WebHookContentType string
WebHookParamNames []string
WebHookParamValues []string
WebHookBody string
ScriptType string
ScriptPath string
ScriptLang string
ScriptCode string
ScriptCwd string
ScriptEnvNames []string
ScriptEnvValues []string
DingTalkWebHookURL string
QyWeixinCorporateId string
QyWeixinAgentId string
QyWeixinAppSecret string
QyWeixinTextFormat string
QyWeixinRobotWebHookURL string
QyWeixinRobotTextFormat string
AliyunSmsSign string
AliyunSmsTemplateCode string
AliyunSmsTemplateVarNames []string
AliyunSmsTemplateVarValues []string
AliyunSmsAccessKeyId string
AliyunSmsAccessKeySecret string
TelegramToken string
TelegramProxyScheme string
TelegramProxyHost string
RateMinutes int32
RateCount int32
HashLife int32
Description string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入媒介名称").
Field("mediaType", params.MediaType).
Require("请选择媒介类型")
options := maps.Map{}
switch params.MediaType {
case "email":
params.Must.
Field("emailSmtp", params.EmailSmtp).
Require("请输入SMTP地址").
Field("emailUsername", params.EmailUsername).
Require("请输入邮箱账号").
Field("emailPassword", params.EmailPassword).
Require("请输入密码或授权码")
options["smtp"] = params.EmailSmtp
options["username"] = params.EmailUsername
options["password"] = params.EmailPassword
options["from"] = params.EmailFrom
case "webHook":
params.Must.
Field("webHookURL", params.WebHookURL).
Require("请输入URL地址").
Match("(?i)^(http|https)://", "URL地址必须以http或https开头").
Field("webHookMethod", params.WebHookMethod).
Require("请选择请求方法")
options["url"] = params.WebHookURL
options["method"] = params.WebHookMethod
options["contentType"] = params.WebHookContentType
headers := []maps.Map{}
if len(params.WebHookHeaderNames) > 0 {
for index, name := range params.WebHookHeaderNames {
if index < len(params.WebHookHeaderValues) {
headers = append(headers, maps.Map{
"name": name,
"value": params.WebHookHeaderValues[index],
})
}
}
}
options["headers"] = headers
if params.WebHookContentType == "params" {
webHookParams := []maps.Map{}
for index, name := range params.WebHookParamNames {
if index < len(params.WebHookParamValues) {
webHookParams = append(webHookParams, maps.Map{
"name": name,
"value": params.WebHookParamValues[index],
})
}
}
options["params"] = webHookParams
} else if params.WebHookContentType == "body" {
options["body"] = params.WebHookBody
}
case "script":
if params.ScriptType == "path" {
params.Must.
Field("scriptPath", params.ScriptPath).
Require("请输入脚本路径")
} else if params.ScriptType == "code" {
params.Must.
Field("scriptCode", params.ScriptCode).
Require("请输入脚本代码")
} else {
params.Must.
Field("scriptPath", params.ScriptPath).
Require("请输入脚本路径")
}
options["scriptType"] = params.ScriptType
options["path"] = params.ScriptPath
options["scriptLang"] = params.ScriptLang
options["script"] = params.ScriptCode
options["cwd"] = params.ScriptCwd
env := []maps.Map{}
for index, envName := range params.ScriptEnvNames {
if index < len(params.ScriptEnvValues) {
env = append(env, maps.Map{
"name": envName,
"value": params.ScriptEnvValues[index],
})
}
}
options["env"] = env
case "dingTalk":
params.Must.
Field("dingTalkWebHookURL", params.DingTalkWebHookURL).
Require("请输入Hook地址").
Match("^https:", "Hook地址必须以https://开头")
options["webHookURL"] = params.DingTalkWebHookURL
case "qyWeixin":
params.Must.
Field("qyWeixinCorporateId", params.QyWeixinCorporateId).
Require("请输入企业ID").
Field("qyWeixinAgentId", params.QyWeixinAgentId).
Require("请输入应用AgentId").
Field("qyWeixinSecret", params.QyWeixinAppSecret).
Require("请输入应用Secret")
options["corporateId"] = params.QyWeixinCorporateId
options["agentId"] = params.QyWeixinAgentId
options["appSecret"] = params.QyWeixinAppSecret
options["textFormat"] = params.QyWeixinTextFormat
case "qyWeixinRobot":
params.Must.
Field("qyWeixinRobotWebHookURL", params.QyWeixinRobotWebHookURL).
Require("请输入WebHook地址").
Match("^https:", "WebHook地址必须以https://开头")
options["webHookURL"] = params.QyWeixinRobotWebHookURL
options["textFormat"] = params.QyWeixinRobotTextFormat
case "aliyunSms":
params.Must.
Field("aliyunSmsSign", params.AliyunSmsSign).
Require("请输入签名名称").
Field("aliyunSmsTemplateCode", params.AliyunSmsTemplateCode).
Require("请输入模板CODE").
Field("aliyunSmsAccessKeyId", params.AliyunSmsAccessKeyId).
Require("请输入AccessKey ID").
Field("aliyunSmsAccessKeySecret", params.AliyunSmsAccessKeySecret).
Require("请输入AccessKey Secret")
options["sign"] = params.AliyunSmsSign
options["templateCode"] = params.AliyunSmsTemplateCode
options["accessKeyId"] = params.AliyunSmsAccessKeyId
options["accessKeySecret"] = params.AliyunSmsAccessKeySecret
variables := []maps.Map{}
for index, name := range params.AliyunSmsTemplateVarNames {
if index < len(params.AliyunSmsTemplateVarValues) {
variables = append(variables, maps.Map{
"name": name,
"value": params.AliyunSmsTemplateVarValues[index],
})
}
}
options["variables"] = variables
case "telegram":
// token
params.Must.
Field("telegramToken", params.TelegramToken).
Require("请输入机器人Token").
Match(`^\w+:[\w-_]+$`, "机器人Token格式错误请修正")
options["token"] = params.TelegramToken
// proxy
var telegramScheme = params.TelegramProxyScheme
var telegramProxyHost = params.TelegramProxyHost
if len(telegramScheme) > 0 && len(telegramProxyHost) > 0 {
// 检查是否为url
if regexp.MustCompile(`^\w+://`).MatchString(telegramProxyHost) {
u, err := url.Parse(telegramProxyHost)
if err == nil {
telegramScheme = u.Scheme
telegramProxyHost = u.Host
}
}
var slashIndex = strings.Index(telegramProxyHost, "/")
if slashIndex >= 0 {
telegramProxyHost = telegramProxyHost[:slashIndex]
}
options["proxyURL"] = telegramScheme + "://" + telegramProxyHost
}
default:
this.Fail("错误的媒介类型")
}
optionsJSON, err := json.Marshal(options)
if err != nil {
this.ErrorPage(err)
return
}
var rateConfig = &monitorconfigs.RateConfig{
Minutes: params.RateMinutes,
Count: params.RateCount,
}
rateJSON, err := json.Marshal(rateConfig)
if err != nil {
this.ErrorPage(err)
return
}
resp, err := this.RPC().MessageMediaInstanceRPC().CreateMessageMediaInstance(this.AdminContext(), &pb.CreateMessageMediaInstanceRequest{
Name: params.Name,
MediaType: params.MediaType,
ParamsJSON: optionsJSON,
Description: params.Description,
RateJSON: rateJSON,
HashLife: params.HashLife,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo(codes.MessageMediaInstance_LogCreateMessageMediaInstance, resp.MessageMediaInstanceId)
this.Success()
}

View File

@@ -0,0 +1,25 @@
package instances
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 {
InstanceId int64
}) {
defer this.CreateLogInfo(codes.MessageMediaInstance_LogDeleteMessageMediaInstance, params.InstanceId)
_, err := this.RPC().MessageMediaInstanceRPC().DeleteMessageMediaInstance(this.AdminContext(), &pb.DeleteMessageMediaInstanceRequest{MessageMediaInstanceId: params.InstanceId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,61 @@
package instances
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("", "", "instance")
}
func (this *IndexAction) RunGet(params struct {
}) {
// TODO 增加系统用户、媒介类型等条件搜索
countResp, err := this.RPC().MessageMediaInstanceRPC().CountAllEnabledMessageMediaInstances(this.AdminContext(), &pb.CountAllEnabledMessageMediaInstancesRequest{
MediaType: "",
Keyword: "",
})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
instancesResp, err := this.RPC().MessageMediaInstanceRPC().ListEnabledMessageMediaInstances(this.AdminContext(), &pb.ListEnabledMessageMediaInstancesRequest{
MediaType: "",
Keyword: "",
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
instanceMaps := []maps.Map{}
for _, instance := range instancesResp.MessageMediaInstances {
if instance.MessageMedia == nil {
continue
}
instanceMaps = append(instanceMaps, maps.Map{
"id": instance.Id,
"name": instance.Name,
"isOn": instance.IsOn,
"media": maps.Map{
"name": instance.MessageMedia.Name,
},
"description": instance.Description,
})
}
this.Data["instances"] = instanceMaps
this.Show()
}

View File

@@ -0,0 +1,25 @@
package instances
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.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Data("teaSubMenu", "instances").
Prefix("/admins/recipients/instances").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/update", new(UpdateAction)).
Post("/delete", new(DeleteAction)).
Get("/instance", new(InstanceAction)).
GetPost("/test", new(TestAction)).
Post("/options", new(OptionsAction)).
EndAll()
})
}

View File

@@ -0,0 +1,80 @@
package instances
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/monitorconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"strings"
)
type InstanceAction struct {
actionutils.ParentAction
}
func (this *InstanceAction) Init() {
this.Nav("", "", "instance")
}
func (this *InstanceAction) RunGet(params struct {
InstanceId int64
}) {
instanceResp, err := this.RPC().MessageMediaInstanceRPC().FindEnabledMessageMediaInstance(this.AdminContext(), &pb.FindEnabledMessageMediaInstanceRequest{MessageMediaInstanceId: params.InstanceId})
if err != nil {
this.ErrorPage(err)
return
}
var instance = instanceResp.MessageMediaInstance
if instance == nil || instance.MessageMedia == nil {
this.NotFound("messageMediaInstance", params.InstanceId)
return
}
// 参数
var mediaParams = maps.Map{}
if len(instance.ParamsJSON) > 0 {
err = json.Unmarshal(instance.ParamsJSON, &mediaParams)
if err != nil {
this.ErrorPage(err)
return
}
// 隐藏密码
if mediaParams.Has("password") {
var password = mediaParams.Get("password")
if password != nil {
passwordString, ok := password.(string)
if ok {
mediaParams["password"] = strings.Repeat("*", len(passwordString))
}
}
}
}
// 频率
var rateConfig = &monitorconfigs.RateConfig{}
if len(instance.RateJSON) > 0 {
err = json.Unmarshal(instance.RateJSON, rateConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["instance"] = maps.Map{
"id": instance.Id,
"name": instance.Name,
"isOn": instance.IsOn,
"media": maps.Map{
"type": instance.MessageMedia.Type,
"name": instance.MessageMedia.Name,
},
"description": instance.Description,
"params": mediaParams,
"rate": rateConfig,
"hashLife": instance.HashLife,
}
this.Show()
}

View File

@@ -0,0 +1,39 @@
package instances
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
// OptionsAction 媒介类型选项
type OptionsAction struct {
actionutils.ParentAction
}
func (this *OptionsAction) RunPost(params struct{}) {
resp, err := this.RPC().MessageMediaInstanceRPC().ListEnabledMessageMediaInstances(this.AdminContext(), &pb.ListEnabledMessageMediaInstancesRequest{
Offset: 0,
Size: 1000,
})
if err != nil {
this.ErrorPage(err)
return
}
instanceMaps := []maps.Map{}
for _, instance := range resp.MessageMediaInstances {
instanceMaps = append(instanceMaps, maps.Map{
"id": instance.Id,
"name": instance.Name,
"description": instance.Description,
"media": maps.Map{
"type": instance.MessageMedia.Type,
"name": instance.MessageMedia.Name,
"userDescription": instance.MessageMedia.UserDescription,
},
})
}
this.Data["instances"] = instanceMaps
this.Success()
}

View File

@@ -0,0 +1,88 @@
package instances
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type TestAction struct {
actionutils.ParentAction
}
func (this *TestAction) Init() {
this.Nav("", "", "test")
}
func (this *TestAction) RunGet(params struct {
InstanceId int64
}) {
instanceResp, err := this.RPC().MessageMediaInstanceRPC().FindEnabledMessageMediaInstance(this.AdminContext(), &pb.FindEnabledMessageMediaInstanceRequest{MessageMediaInstanceId: params.InstanceId})
if err != nil {
this.ErrorPage(err)
return
}
instance := instanceResp.MessageMediaInstance
if instance == nil || instance.MessageMedia == nil {
this.NotFound("messageMediaInstance", params.InstanceId)
return
}
mediaParams := maps.Map{}
if len(instance.ParamsJSON) > 0 {
err = json.Unmarshal(instance.ParamsJSON, &mediaParams)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["instance"] = maps.Map{
"id": instance.Id,
"isOn": instance.IsOn,
"media": maps.Map{
"type": instance.MessageMedia.Type,
"name": instance.MessageMedia.Name,
"userDescription": instance.MessageMedia.UserDescription,
},
"description": instance.Description,
"params": mediaParams,
}
this.Show()
}
func (this *TestAction) RunPost(params struct {
InstanceId int64
Subject string
Body string
User string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("instanceId", params.InstanceId).
Gt(0, "请选择正确的媒介")
resp, err := this.RPC().MessageTaskRPC().SendMessageTask(this.AdminContext(), &pb.SendMessageTaskRequest{
MessageMediaInstanceId: params.InstanceId,
User: params.User,
Subject: params.Subject,
Body: params.Body,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["result"] = maps.Map{
"isOk": resp.IsOk,
"error": resp.Error,
"response": resp.Response,
}
this.Success()
}

View File

@@ -0,0 +1,360 @@
package instances
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/monitorconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"net/url"
"regexp"
"strings"
)
type UpdateAction struct {
actionutils.ParentAction
}
func (this *UpdateAction) Init() {
this.Nav("", "", "update")
}
func (this *UpdateAction) RunGet(params struct {
InstanceId int64
}) {
instanceResp, err := this.RPC().MessageMediaInstanceRPC().FindEnabledMessageMediaInstance(this.AdminContext(), &pb.FindEnabledMessageMediaInstanceRequest{MessageMediaInstanceId: params.InstanceId})
if err != nil {
this.ErrorPage(err)
return
}
var instance = instanceResp.MessageMediaInstance
if instance == nil || instance.MessageMedia == nil {
this.NotFound("messageMediaInstance", params.InstanceId)
return
}
var mediaParams = maps.Map{}
if len(instance.ParamsJSON) > 0 {
err = json.Unmarshal(instance.ParamsJSON, &mediaParams)
if err != nil {
this.ErrorPage(err)
return
}
}
// telegram parameters
this.Data["telegramProxyScheme"] = "socks5"
this.Data["telegramProxyHost"] = ""
if instance.MessageMedia != nil && instance.MessageMedia.Type == "telegram" {
var proxyURL = mediaParams.GetString("proxyURL")
var reg = regexp.MustCompile(`^(\w+)://(.+)$`)
if reg.MatchString(proxyURL) {
var matches = reg.FindStringSubmatch(proxyURL)
if len(matches) > 2 {
this.Data["telegramProxyScheme"] = matches[1]
this.Data["telegramProxyHost"] = matches[2]
}
}
}
var rateConfig = &monitorconfigs.RateConfig{}
if len(instance.RateJSON) > 0 {
err = json.Unmarshal(instance.RateJSON, rateConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["instance"] = maps.Map{
"id": instance.Id,
"name": instance.Name,
"isOn": instance.IsOn,
"media": maps.Map{
"type": instance.MessageMedia.Type,
"name": instance.MessageMedia.Name,
},
"description": instance.Description,
"params": mediaParams,
"rate": rateConfig,
"hashLife": instance.HashLife,
}
this.Show()
}
func (this *UpdateAction) RunPost(params struct {
InstanceId int64
Name string
MediaType string
EmailSmtp string
EmailUsername string
EmailPassword string
EmailFrom string
WebHookURL string
WebHookMethod string
WebHookHeaderNames []string
WebHookHeaderValues []string
WebHookContentType string
WebHookParamNames []string
WebHookParamValues []string
WebHookBody string
ScriptType string
ScriptPath string
ScriptLang string
ScriptCode string
ScriptCwd string
ScriptEnvNames []string
ScriptEnvValues []string
DingTalkWebHookURL string
QyWeixinCorporateId string
QyWeixinAgentId string
QyWeixinAppSecret string
QyWeixinTextFormat string
QyWeixinRobotWebHookURL string
QyWeixinRobotTextFormat string
AliyunSmsSign string
AliyunSmsTemplateCode string
AliyunSmsTemplateVarNames []string
AliyunSmsTemplateVarValues []string
AliyunSmsAccessKeyId string
AliyunSmsAccessKeySecret string
TelegramToken string
TelegramProxyScheme string
TelegramProxyHost string
GroupIds string
Description string
IsOn bool
RateMinutes int32
RateCount int32
HashLife int32
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.MessageMediaInstance_LogUpdateMessageMediaInstance, params.InstanceId)
params.Must.
Field("name", params.Name).
Require("请输入媒介名称").
Field("mediaType", params.MediaType).
Require("请选择媒介类型")
options := maps.Map{}
switch params.MediaType {
case "email":
params.Must.
Field("emailSmtp", params.EmailSmtp).
Require("请输入SMTP地址").
Field("emailUsername", params.EmailUsername).
Require("请输入邮箱账号").
Field("emailPassword", params.EmailPassword).
Require("请输入密码或授权码")
options["smtp"] = params.EmailSmtp
options["username"] = params.EmailUsername
options["password"] = params.EmailPassword
options["from"] = params.EmailFrom
case "webHook":
params.Must.
Field("webHookURL", params.WebHookURL).
Require("请输入URL地址").
Match("(?i)^(http|https)://", "URL地址必须以http或https开头").
Field("webHookMethod", params.WebHookMethod).
Require("请选择请求方法")
options["url"] = params.WebHookURL
options["method"] = params.WebHookMethod
options["contentType"] = params.WebHookContentType
headers := []maps.Map{}
if len(params.WebHookHeaderNames) > 0 {
for index, name := range params.WebHookHeaderNames {
if index < len(params.WebHookHeaderValues) {
headers = append(headers, maps.Map{
"name": name,
"value": params.WebHookHeaderValues[index],
})
}
}
}
options["headers"] = headers
if params.WebHookContentType == "params" {
webHookParams := []maps.Map{}
for index, name := range params.WebHookParamNames {
if index < len(params.WebHookParamValues) {
webHookParams = append(webHookParams, maps.Map{
"name": name,
"value": params.WebHookParamValues[index],
})
}
}
options["params"] = webHookParams
} else if params.WebHookContentType == "body" {
options["body"] = params.WebHookBody
}
case "script":
if params.ScriptType == "path" {
params.Must.
Field("scriptPath", params.ScriptPath).
Require("请输入脚本路径")
} else if params.ScriptType == "code" {
params.Must.
Field("scriptCode", params.ScriptCode).
Require("请输入脚本代码")
} else {
params.Must.
Field("scriptPath", params.ScriptPath).
Require("请输入脚本路径")
}
options["scriptType"] = params.ScriptType
options["path"] = params.ScriptPath
options["scriptLang"] = params.ScriptLang
options["script"] = params.ScriptCode
options["cwd"] = params.ScriptCwd
env := []maps.Map{}
for index, envName := range params.ScriptEnvNames {
if index < len(params.ScriptEnvValues) {
env = append(env, maps.Map{
"name": envName,
"value": params.ScriptEnvValues[index],
})
}
}
options["env"] = env
case "dingTalk":
params.Must.
Field("dingTalkWebHookURL", params.DingTalkWebHookURL).
Require("请输入Hook地址").
Match("^https:", "Hook地址必须以https://开头")
options["webHookURL"] = params.DingTalkWebHookURL
case "qyWeixin":
params.Must.
Field("qyWeixinCorporateId", params.QyWeixinCorporateId).
Require("请输入企业ID").
Field("qyWeixinAgentId", params.QyWeixinAgentId).
Require("请输入应用AgentId").
Field("qyWeixinSecret", params.QyWeixinAppSecret).
Require("请输入应用Secret")
options["corporateId"] = params.QyWeixinCorporateId
options["agentId"] = params.QyWeixinAgentId
options["appSecret"] = params.QyWeixinAppSecret
options["textFormat"] = params.QyWeixinTextFormat
case "qyWeixinRobot":
params.Must.
Field("qyWeixinRobotWebHookURL", params.QyWeixinRobotWebHookURL).
Require("请输入WebHook地址").
Match("^https:", "WebHook地址必须以https://开头")
options["webHookURL"] = params.QyWeixinRobotWebHookURL
options["textFormat"] = params.QyWeixinRobotTextFormat
case "aliyunSms":
params.Must.
Field("aliyunSmsSign", params.AliyunSmsSign).
Require("请输入签名名称").
Field("aliyunSmsTemplateCode", params.AliyunSmsTemplateCode).
Require("请输入模板CODE").
Field("aliyunSmsAccessKeyId", params.AliyunSmsAccessKeyId).
Require("请输入AccessKey ID").
Field("aliyunSmsAccessKeySecret", params.AliyunSmsAccessKeySecret).
Require("请输入AccessKey Secret")
options["sign"] = params.AliyunSmsSign
options["templateCode"] = params.AliyunSmsTemplateCode
options["accessKeyId"] = params.AliyunSmsAccessKeyId
options["accessKeySecret"] = params.AliyunSmsAccessKeySecret
variables := []maps.Map{}
for index, name := range params.AliyunSmsTemplateVarNames {
if index < len(params.AliyunSmsTemplateVarValues) {
variables = append(variables, maps.Map{
"name": name,
"value": params.AliyunSmsTemplateVarValues[index],
})
}
}
options["variables"] = variables
case "telegram":
params.Must.
Field("telegramToken", params.TelegramToken).
Require("请输入机器人Token").
Match(`^\w+:[\w-_]+$`, "机器人Token格式错误请修正")
options["token"] = params.TelegramToken
// proxy
var telegramScheme = params.TelegramProxyScheme
var telegramProxyHost = params.TelegramProxyHost
if len(telegramScheme) > 0 && len(telegramProxyHost) > 0 {
// 检查是否为url
if regexp.MustCompile(`^\w+://`).MatchString(telegramProxyHost) {
u, err := url.Parse(telegramProxyHost)
if err == nil {
telegramScheme = u.Scheme
telegramProxyHost = u.Host
}
}
var slashIndex = strings.Index(telegramProxyHost, "/")
if slashIndex >= 0 {
telegramProxyHost = telegramProxyHost[:slashIndex]
}
options["proxyURL"] = telegramScheme + "://" + telegramProxyHost
}
default:
this.Fail("错误的媒介类型")
}
optionsJSON, err := json.Marshal(options)
if err != nil {
this.ErrorPage(err)
return
}
var rateConfig = &monitorconfigs.RateConfig{
Minutes: params.RateMinutes,
Count: params.RateCount,
}
rateJSON, err := json.Marshal(rateConfig)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().MessageMediaInstanceRPC().UpdateMessageMediaInstance(this.AdminContext(), &pb.UpdateMessageMediaInstanceRequest{
MessageMediaInstanceId: params.InstanceId,
Name: params.Name,
MediaType: params.MediaType,
ParamsJSON: optionsJSON,
Description: params.Description,
RateJSON: rateJSON,
HashLife: params.HashLife,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,61 @@
package logs
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 IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "log")
}
func (this *IndexAction) RunGet(params struct{}) {
countResp, err := this.RPC().MessageTaskLogRPC().CountMessageTaskLogs(this.AdminContext(), &pb.CountMessageTaskLogsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var page = this.NewPage(countResp.Count)
this.Data["page"] = page.AsHTML()
logsResp, err := this.RPC().MessageTaskLogRPC().ListMessageTaskLogs(this.AdminContext(), &pb.ListMessageTaskLogsRequest{
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var logMaps = []maps.Map{}
for _, log := range logsResp.MessageTaskLogs {
if log.MessageTask.MessageRecipient != nil {
log.MessageTask.User = log.MessageTask.MessageRecipient.User
}
logMaps = append(logMaps, maps.Map{
"task": maps.Map{
"id": log.MessageTask.Id,
"user": log.MessageTask.User,
"subject": log.MessageTask.Subject,
"body": log.MessageTask.Body,
"instance": maps.Map{
"id": log.MessageTask.MessageMediaInstance.Id,
"name": log.MessageTask.MessageMediaInstance.Name,
},
},
"isOk": log.IsOk,
"error": log.Error,
"response": log.Response,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", log.CreatedAt),
})
}
this.Data["logs"] = logMaps
this.Show()
}

View File

@@ -0,0 +1,20 @@
package logs
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.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Data("teaSubMenu", "recipients").
Prefix("/admins/recipients/logs").
Get("", new(IndexAction)).
Post("/updateTaskStatus", new(UpdateTaskStatusAction)).
EndAll()
})
}

View File

@@ -0,0 +1,31 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package logs
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type UpdateTaskStatusAction struct {
actionutils.ParentAction
}
func (this *UpdateTaskStatusAction) RunPost(params struct {
TaskId int64
Status int32
}) {
defer this.CreateLogInfo(codes.MessageTask_LogUpdateMessageTaskStatus, params.TaskId, pb.UpdateMessageTaskStatusRequest_MessageTaskStatusNone)
_, err := this.RPC().MessageTaskRPC().UpdateMessageTaskStatus(this.AdminContext(), &pb.UpdateMessageTaskStatusRequest{
MessageTaskId: params.TaskId,
Status: pb.UpdateMessageTaskStatusRequest_Status(params.Status),
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,32 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
// 媒介类型选项
type MediaOptionsAction struct {
actionutils.ParentAction
}
func (this *MediaOptionsAction) RunPost(params struct{}) {
resp, err := this.RPC().MessageMediaRPC().FindAllMessageMedias(this.AdminContext(), &pb.FindAllMessageMediasRequest{})
if err != nil {
this.ErrorPage(err)
return
}
mediaMaps := []maps.Map{}
for _, media := range resp.MessageMedias {
mediaMaps = append(mediaMaps, maps.Map{
"id": media.Id,
"type": media.Type,
"name": media.Name,
"description": media.Description,
})
}
this.Data["medias"] = mediaMaps
this.Success()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package receivers
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 {
ReceiverId int64
}) {
defer this.CreateLogInfo(codes.MessageReceiver_LogDeleteReceiver, params.ReceiverId)
_, err := this.RPC().MessageReceiverRPC().DeleteMessageReceiver(this.AdminContext(), &pb.DeleteMessageReceiverRequest{MessageReceiverId: params.ReceiverId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,18 @@
package receivers
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.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Prefix("/admins/recipients/receivers").
Post("/delete", new(DeleteAction)).
EndAll()
})
}

View File

@@ -0,0 +1,83 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type RecipientAction struct {
actionutils.ParentAction
}
func (this *RecipientAction) Init() {
this.Nav("", "", "recipient")
}
func (this *RecipientAction) RunGet(params struct {
RecipientId int64
}) {
recipientResp, err := this.RPC().MessageRecipientRPC().FindEnabledMessageRecipient(this.AdminContext(), &pb.FindEnabledMessageRecipientRequest{MessageRecipientId: params.RecipientId})
if err != nil {
this.ErrorPage(err)
return
}
var recipient = recipientResp.MessageRecipient
if recipient == nil || recipient.Admin == nil || recipient.MessageMediaInstance == nil {
this.NotFound("messageRecipient", params.RecipientId)
return
}
// 已关联的接收者
receiversResp, err := this.RPC().MessageReceiverRPC().FindAllEnabledMessageReceiversWithMessageRecipientId(this.AdminContext(), &pb.FindAllEnabledMessageReceiversWithMessageRecipientIdRequest{
MessageRecipientId: params.RecipientId,
})
if err != nil {
this.ErrorPage(err)
return
}
// node clusters
var nodeClusterMaps = []maps.Map{}
for _, receiver := range receiversResp.MessageReceivers {
if receiver.Role == nodeconfigs.NodeRoleNode && receiver.ClusterId > 0 && receiver.NodeId == 0 && receiver.ServerId == 0 {
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: receiver.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var cluster = clusterResp.NodeCluster
if cluster != nil {
nodeClusterMaps = append(nodeClusterMaps, maps.Map{
"receiverId": receiver.Id,
"id": cluster.Id,
"name": cluster.Name,
})
}
}
}
this.Data["nodeClusters"] = nodeClusterMaps
this.Data["recipient"] = maps.Map{
"id": recipient.Id,
"admin": maps.Map{
"id": recipient.Admin.Id,
"fullname": recipient.Admin.Fullname,
"username": recipient.Admin.Username,
},
"groups": recipient.MessageRecipientGroups,
"isOn": recipient.IsOn,
"instance": maps.Map{
"id": recipient.MessageMediaInstance.Id,
"name": recipient.MessageMediaInstance.Name,
"description": recipient.MessageMediaInstance.Description,
},
"user": recipient.User,
"description": recipient.Description,
"timeFrom": recipient.TimeFrom,
"timeTo": recipient.TimeTo,
}
this.Show()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package tasks
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 {
TaskId int64
}) {
defer this.CreateLogInfo(codes.MessageTask_LogDeleteMessageTask, params.TaskId)
_, err := this.RPC().MessageTaskRPC().DeleteMessageTask(this.AdminContext(), &pb.DeleteMessageTaskRequest{MessageTaskId: params.TaskId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,103 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package tasks
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 IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "task")
}
func (this *IndexAction) RunGet(params struct {
Status int32
}) {
this.Data["status"] = params.Status
if params.Status > 3 {
params.Status = 0
}
countWaitingResp, err := this.RPC().MessageTaskRPC().CountMessageTasksWithStatus(this.AdminContext(), &pb.CountMessageTasksWithStatusRequest{Status: pb.CountMessageTasksWithStatusRequest_MessageTaskStatusNone})
if err != nil {
this.ErrorPage(err)
return
}
var countWaiting = countWaitingResp.Count
this.Data["countWaiting"] = countWaiting
countFailedResp, err := this.RPC().MessageTaskRPC().CountMessageTasksWithStatus(this.AdminContext(), &pb.CountMessageTasksWithStatusRequest{Status: pb.CountMessageTasksWithStatusRequest_MessageTaskStatusFailed})
if err != nil {
this.ErrorPage(err)
return
}
var countFailed = countFailedResp.Count
this.Data["countFailed"] = countFailed
// 列表
var total = int64(0)
switch params.Status {
case 0:
total = countWaiting
case 3:
total = countFailed
}
page := this.NewPage(total)
this.Data["page"] = page.AsHTML()
var taskMaps = []maps.Map{}
tasksResp, err := this.RPC().MessageTaskRPC().ListMessageTasksWithStatus(this.AdminContext(), &pb.ListMessageTasksWithStatusRequest{
Status: pb.ListMessageTasksWithStatusRequest_Status(params.Status),
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
for _, task := range tasksResp.MessageTasks {
var resultMap = maps.Map{}
var result = task.Result
if result != nil {
resultMap = maps.Map{
"isOk": result.IsOk,
"error": result.Error,
"response": result.Response,
}
}
//var recipients = []string{}
var user = ""
var instanceMap maps.Map
if task.MessageRecipient != nil {
user = task.MessageRecipient.User
if task.MessageRecipient.MessageMediaInstance != nil {
instanceMap = maps.Map{
"id": task.MessageRecipient.MessageMediaInstance.Id,
"name": task.MessageRecipient.MessageMediaInstance.Name,
}
}
}
taskMaps = append(taskMaps, maps.Map{
"id": task.Id,
"subject": task.Subject,
"body": task.Body,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", task.CreatedAt),
"result": resultMap,
"status": task.Status,
"user": user,
"instance": instanceMap,
})
}
this.Data["tasks"] = taskMaps
this.Show()
}

View File

@@ -0,0 +1,21 @@
package tasks
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.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Data("teaSubMenu", "recipients").
Prefix("/admins/recipients/tasks").
Get("", new(IndexAction)).
Post("/taskInfo", new(TaskInfoAction)).
Post("/delete", new(DeleteAction)).
EndAll()
})
}

View File

@@ -0,0 +1,40 @@
package tasks
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type TaskInfoAction struct {
actionutils.ParentAction
}
func (this *TaskInfoAction) Init() {
this.Nav("", "", "")
}
func (this *TaskInfoAction) RunPost(params struct {
TaskId int64
}) {
resp, err := this.RPC().MessageTaskRPC().FindEnabledMessageTask(this.AdminContext(), &pb.FindEnabledMessageTaskRequest{MessageTaskId: params.TaskId})
if err != nil {
this.ErrorPage(err)
return
}
if resp.MessageTask == nil {
this.NotFound("messageTask", params.TaskId)
return
}
result := resp.MessageTask.Result
this.Data["status"] = resp.MessageTask.Status
this.Data["result"] = maps.Map{
"isOk": result.IsOk,
"response": result.Response,
"error": result.Error,
}
this.Success()
}

View File

@@ -0,0 +1,114 @@
package recipients
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type TestAction struct {
actionutils.ParentAction
}
func (this *TestAction) Init() {
this.Nav("", "", "test")
}
func (this *TestAction) RunGet(params struct {
RecipientId int64
}) {
recipientResp, err := this.RPC().MessageRecipientRPC().FindEnabledMessageRecipient(this.AdminContext(), &pb.FindEnabledMessageRecipientRequest{MessageRecipientId: params.RecipientId})
if err != nil {
this.ErrorPage(err)
return
}
recipient := recipientResp.MessageRecipient
if recipient == nil || recipient.Admin == nil || recipient.MessageMediaInstance == nil {
this.NotFound("messageRecipient", params.RecipientId)
return
}
this.Data["recipient"] = maps.Map{
"id": recipient.Id,
"admin": maps.Map{
"id": recipient.Admin.Id,
"fullname": recipient.Admin.Fullname,
"username": recipient.Admin.Username,
},
"instance": maps.Map{
"id": recipient.MessageMediaInstance.Id,
"name": recipient.MessageMediaInstance.Name,
"description": recipient.MessageMediaInstance.Description,
},
"user": recipient.User,
}
instanceResp, err := this.RPC().MessageMediaInstanceRPC().FindEnabledMessageMediaInstance(this.AdminContext(), &pb.FindEnabledMessageMediaInstanceRequest{MessageMediaInstanceId: recipient.MessageMediaInstance.Id})
if err != nil {
this.ErrorPage(err)
return
}
instance := instanceResp.MessageMediaInstance
if instance == nil || instance.MessageMedia == nil {
this.NotFound("messageMediaInstance", recipient.MessageMediaInstance.Id)
return
}
mediaParams := maps.Map{}
if len(instance.ParamsJSON) > 0 {
err = json.Unmarshal(instance.ParamsJSON, &mediaParams)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["instance"] = maps.Map{
"id": instance.Id,
"isOn": instance.IsOn,
"media": maps.Map{
"type": instance.MessageMedia.Type,
"name": instance.MessageMedia.Name,
"userDescription": instance.MessageMedia.UserDescription,
},
"description": instance.Description,
"params": mediaParams,
}
this.Show()
}
func (this *TestAction) RunPost(params struct {
InstanceId int64
Subject string
Body string
User string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("instanceId", params.InstanceId).
Gt(0, "请选择正确的媒介")
resp, err := this.RPC().MessageTaskRPC().SendMessageTask(this.AdminContext(), &pb.SendMessageTaskRequest{
MessageMediaInstanceId: params.InstanceId,
User: params.User,
Subject: params.Subject,
Body: params.Body,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["result"] = maps.Map{
"isOk": resp.IsOk,
"error": resp.Error,
"response": resp.Response,
}
this.Success()
}

View File

@@ -0,0 +1,181 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"regexp"
"strings"
)
type UpdateAction struct {
actionutils.ParentAction
}
func (this *UpdateAction) Init() {
this.Nav("", "", "update")
}
func (this *UpdateAction) RunGet(params struct {
RecipientId int64
}) {
recipientResp, err := this.RPC().MessageRecipientRPC().FindEnabledMessageRecipient(this.AdminContext(), &pb.FindEnabledMessageRecipientRequest{MessageRecipientId: params.RecipientId})
if err != nil {
this.ErrorPage(err)
return
}
var recipient = recipientResp.MessageRecipient
if recipient == nil || recipient.Admin == nil || recipient.MessageMediaInstance == nil {
this.NotFound("messageRecipient", params.RecipientId)
return
}
var timeFromHour = ""
var timeFromMinute = ""
var timeFromSecond = ""
if len(recipient.TimeFrom) > 0 {
var pieces = strings.Split(recipient.TimeFrom, ":")
timeFromHour = pieces[0]
timeFromMinute = pieces[1]
timeFromSecond = pieces[2]
}
var timeToHour = ""
var timeToMinute = ""
var timeToSecond = ""
if len(recipient.TimeTo) > 0 {
var pieces = strings.Split(recipient.TimeTo, ":")
timeToHour = pieces[0]
timeToMinute = pieces[1]
timeToSecond = pieces[2]
}
this.Data["recipient"] = maps.Map{
"id": recipient.Id,
"admin": maps.Map{
"id": recipient.Admin.Id,
"fullname": recipient.Admin.Fullname,
"username": recipient.Admin.Username,
},
"groups": recipient.MessageRecipientGroups,
"isOn": recipient.IsOn,
"instance": maps.Map{
"id": recipient.MessageMediaInstance.Id,
"name": recipient.MessageMediaInstance.Name,
},
"user": recipient.User,
"description": recipient.Description,
"timeFromHour": timeFromHour,
"timeFromMinute": timeFromMinute,
"timeFromSecond": timeFromSecond,
"timeToHour": timeToHour,
"timeToMinute": timeToMinute,
"timeToSecond": timeToSecond,
}
// receivers
var nodeClusterIds []int64
receiversResp, err := this.RPC().MessageReceiverRPC().FindAllEnabledMessageReceiversWithMessageRecipientId(this.AdminContext(), &pb.FindAllEnabledMessageReceiversWithMessageRecipientIdRequest{MessageRecipientId: params.RecipientId})
if err != nil {
this.ErrorPage(err)
return
}
for _, receiver := range receiversResp.MessageReceivers {
if receiver.Role == nodeconfigs.NodeRoleNode {
if receiver.ClusterId > 0 && receiver.NodeId == 0 && receiver.ServerId == 0 {
nodeClusterIds = append(nodeClusterIds, receiver.ClusterId)
}
}
}
// node clusters
nodeClustersResp, err := this.RPC().NodeClusterRPC().FindAllEnabledNodeClusters(this.AdminContext(), &pb.FindAllEnabledNodeClustersRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var nodeClusterMaps = []maps.Map{}
for _, nodeCluster := range nodeClustersResp.NodeClusters {
nodeClusterMaps = append(nodeClusterMaps, maps.Map{
"id": nodeCluster.Id,
"name": nodeCluster.Name,
"isChecked": lists.ContainsInt64(nodeClusterIds, nodeCluster.Id),
})
}
this.Data["nodeClusters"] = nodeClusterMaps
this.Show()
}
func (this *UpdateAction) RunPost(params struct {
RecipientId int64
AdminId int64
User string
InstanceId int64
GroupIds string
Description string
IsOn bool
TimeFromHour string
TimeFromMinute string
TimeFromSecond string
TimeToHour string
TimeToMinute string
TimeToSecond string
NodeClusterIds []int64
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.MessageRecipient_LogUpdateMessageRecipient, params.RecipientId)
params.Must.
Field("adminId", params.AdminId).
Gt(0, "请选择系统用户").
Field("instanceId", params.InstanceId).
Gt(0, "请选择媒介")
groupIds := utils.SplitNumbers(params.GroupIds)
var digitReg = regexp.MustCompile(`^\d+$`)
var timeFrom = ""
if digitReg.MatchString(params.TimeFromHour) && digitReg.MatchString(params.TimeFromMinute) && digitReg.MatchString(params.TimeFromSecond) {
timeFrom = params.TimeFromHour + ":" + params.TimeFromMinute + ":" + params.TimeFromSecond
}
var timeTo = ""
if digitReg.MatchString(params.TimeToHour) && digitReg.MatchString(params.TimeToMinute) && digitReg.MatchString(params.TimeToSecond) {
timeTo = params.TimeToHour + ":" + params.TimeToMinute + ":" + params.TimeToSecond
}
_, err := this.RPC().MessageRecipientRPC().UpdateMessageRecipient(this.AdminContext(), &pb.UpdateMessageRecipientRequest{
MessageRecipientId: params.RecipientId,
AdminId: params.AdminId,
MessageMediaInstanceId: params.InstanceId,
User: params.User,
MessageRecipientGroupIds: groupIds,
Description: params.Description,
IsOn: params.IsOn,
TimeFrom: timeFrom,
TimeTo: timeTo,
NodeClusterIds: params.NodeClusterIds,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,215 @@
package admins
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"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/systemconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/xlzd/gotp"
)
type UpdateAction struct {
actionutils.ParentAction
}
func (this *UpdateAction) Init() {
this.Nav("", "", "update")
}
func (this *UpdateAction) RunGet(params struct {
AdminId int64
}) {
adminResp, err := this.RPC().AdminRPC().FindEnabledAdmin(this.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: params.AdminId})
if err != nil {
this.ErrorPage(err)
return
}
var admin = adminResp.Admin
if admin == nil {
this.NotFound("admin", params.AdminId)
return
}
// OTP认证
var otpLoginIsOn = false
if admin.OtpLogin != nil {
otpLoginIsOn = admin.OtpLogin.IsOn
}
// AccessKey数量
countAccessKeyResp, err := this.RPC().UserAccessKeyRPC().CountAllEnabledUserAccessKeys(this.AdminContext(), &pb.CountAllEnabledUserAccessKeysRequest{AdminId: params.AdminId})
if err != nil {
this.ErrorPage(err)
return
}
var countAccessKeys = countAccessKeyResp.Count
this.Data["admin"] = maps.Map{
"id": admin.Id,
"fullname": admin.Fullname,
"username": admin.Username,
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"canLogin": admin.CanLogin,
"otpLoginIsOn": otpLoginIsOn,
"countAccessKeys": countAccessKeys,
}
// 权限
var moduleMaps = configloaders.AllModuleMaps(this.LangCode())
for _, m := range moduleMaps {
code := m.GetString("code")
isChecked := false
for _, module := range admin.Modules {
if module.Code == code {
isChecked = true
break
}
}
m["isChecked"] = isChecked
}
this.Data["modules"] = moduleMaps
this.Show()
}
func (this *UpdateAction) RunPost(params struct {
AdminId int64
Fullname string
Username string
Pass1 string
Pass2 string
ModuleCodes []string
IsOn bool
IsSuper bool
CanLogin bool
// OTP
OtpOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.Admin_LogUpdateAdmin, params.AdminId)
params.Must.
Field("fullname", params.Fullname).
Require("请输入系统用户全名")
params.Must.
Field("username", params.Username).
Require("请输入登录用户名").
Match(`^[a-zA-Z0-9_]+$`, "用户名中只能包含英文、数字或下划线")
existsResp, err := this.RPC().AdminRPC().CheckAdminUsername(this.AdminContext(), &pb.CheckAdminUsernameRequest{
AdminId: params.AdminId,
Username: params.Username,
})
if err != nil {
this.ErrorPage(err)
return
}
if existsResp.Exists {
this.FailField("username", "此用户名已经被别的系统用户使用,请换一个")
}
if len(params.Pass1) > 0 {
params.Must.
Field("pass1", params.Pass1).
Require("请输入登录密码").
Field("pass2", params.Pass2).
Require("请输入确认登录密码")
if params.Pass1 != params.Pass2 {
this.FailField("pass2", "两次输入的密码不一致")
}
}
modules := []*systemconfigs.AdminModule{}
for _, code := range params.ModuleCodes {
modules = append(modules, &systemconfigs.AdminModule{
Code: code,
AllowAll: true,
Actions: nil, // TODO 后期再开放细粒度控制
})
}
modulesJSON, err := json.Marshal(modules)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().AdminRPC().UpdateAdmin(this.AdminContext(), &pb.UpdateAdminRequest{
AdminId: params.AdminId,
Username: params.Username,
Password: params.Pass1,
Fullname: params.Fullname,
ModulesJSON: modulesJSON,
IsSuper: params.IsSuper,
IsOn: params.IsOn,
CanLogin: params.CanLogin,
})
if err != nil {
this.ErrorPage(err)
return
}
// 修改OTP
otpLoginResp, err := this.RPC().LoginRPC().FindEnabledLogin(this.AdminContext(), &pb.FindEnabledLoginRequest{
AdminId: params.AdminId,
Type: "otp",
})
if err != nil {
this.ErrorPage(err)
return
}
{
otpLogin := otpLoginResp.Login
if params.OtpOn {
if otpLogin == nil {
otpLogin = &pb.Login{
Id: 0,
Type: "otp",
ParamsJSON: maps.Map{
"secret": gotp.RandomSecret(16), // TODO 改成可以设置secret长度
}.AsJSON(),
IsOn: true,
AdminId: params.AdminId,
UserId: 0,
}
} else {
// 如果已经有了,就覆盖,这样可以保留既有的参数
otpLogin.IsOn = true
}
_, err = this.RPC().LoginRPC().UpdateLogin(this.AdminContext(), &pb.UpdateLoginRequest{Login: otpLogin})
if err != nil {
this.ErrorPage(err)
return
}
} else {
_, err = this.RPC().LoginRPC().UpdateLogin(this.AdminContext(), &pb.UpdateLoginRequest{Login: &pb.Login{
Type: "otp",
IsOn: false,
AdminId: params.AdminId,
}})
if err != nil {
this.ErrorPage(err)
return
}
}
// 通知更改
err = configloaders.NotifyAdminModuleMappingChange()
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}
}

View File

@@ -0,0 +1,111 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package antiddos
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{}) {
// 线路选项
networkResp, err := this.RPC().ADNetworkRPC().FindAllADNetworks(this.AdminContext(), &pb.FindAllADNetworkRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var networkMaps = []maps.Map{}
for _, network := range networkResp.AdNetworks {
networkMaps = append(networkMaps, maps.Map{
"id": network.Id,
"name": network.Name,
"isOn": network.IsOn,
})
}
this.Data["networks"] = networkMaps
// 带宽单位
this.Data["protectionUnitOptions"] = []maps.Map{
{
"code": userconfigs.ADPackageSizeUnitGb,
"name": "Gbps",
},
{
"code": userconfigs.ADPackageSizeUnitTb,
"name": "Tbps",
},
}
this.Data["serverUnitOptions"] = []maps.Map{
{
"code": userconfigs.ADPackageSizeUnitMb,
"name": "Mbps",
},
{
"code": userconfigs.ADPackageSizeUnitGb,
"name": "Gbps",
},
{
"code": userconfigs.ADPackageSizeUnitTb,
"name": "Tbps",
},
}
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
NetworkId int64
ProtectionBandwidthSize int32
ProtectionBandwidthUnit string
ServerBandwidthSize int32
ServerBandwidthUnit string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
var packageId int64
defer func() {
this.CreateLogInfo(codes.ADPackage_LogCreateADPackage, packageId)
}()
if params.NetworkId <= 0 {
this.Fail("请选择所属线路")
return
}
if params.ProtectionBandwidthSize <= 0 || len(params.ProtectionBandwidthUnit) == 0 {
this.FailField("protectionBandwidthSize", "请输入防护带宽")
return
}
if params.ServerBandwidthSize <= 0 || len(params.ServerBandwidthUnit) == 0 {
this.FailField("serverBandwidthSize", "请输入业务带宽")
return
}
createResp, err := this.RPC().ADPackageRPC().CreateADPackage(this.AdminContext(), &pb.CreateADPackageRequest{
AdNetworkId: params.NetworkId,
ProtectionBandwidthSize: params.ProtectionBandwidthSize,
ProtectionBandwidthUnit: params.ProtectionBandwidthUnit,
ServerBandwidthSize: params.ServerBandwidthSize,
ServerBandwidthUnit: params.ServerBandwidthUnit,
})
if err != nil {
this.ErrorPage(err)
return
}
packageId = createResp.AdPackageId
this.Success()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package antiddos
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.ADPackage_LogDeleteADPackage, params.PackageId)
_, err := this.RPC().ADPackageRPC().DeleteADPackage(this.AdminContext(), &pb.DeleteADPackageRequest{AdPackageId: params.PackageId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,116 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package antiddos
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 {
NetworkId int64
}) {
this.Data["networkId"] = params.NetworkId
countResp, err := this.RPC().ADPackageRPC().CountADPackages(this.AdminContext(), &pb.CountADPackagesRequest{
AdNetworkId: params.NetworkId,
})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
var page = this.NewPage(count)
this.Data["page"] = page.AsHTML()
packagesResp, err := this.RPC().ADPackageRPC().ListADPackages(this.AdminContext(), &pb.ListADPackagesRequest{
AdNetworkId: params.NetworkId,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var packageMaps = []maps.Map{}
for _, p := range packagesResp.AdPackages {
// 线路
var networkMap = maps.Map{
"id": 0,
"name": "",
"isOn": false,
}
if p.AdNetwork != nil {
networkMap = maps.Map{
"id": p.AdNetwork.Id,
"name": p.AdNetwork.Name,
"isOn": p.AdNetwork.IsOn,
}
}
// 价格项数量
countPricesResp, err := this.RPC().ADPackagePriceRPC().CountADPackagePrices(this.AdminContext(), &pb.CountADPackagePricesRequest{AdPackageId: p.Id})
if err != nil {
this.ErrorPage(err)
return
}
var countPrices = countPricesResp.Count
// 实例数量
countInstancesResp, err := this.RPC().ADPackageInstanceRPC().CountADPackageInstances(this.AdminContext(), &pb.CountADPackageInstancesRequest{AdPackageId: p.Id})
if err != nil {
this.ErrorPage(err)
return
}
var countInstances = countInstancesResp.Count
packageMaps = append(packageMaps, maps.Map{
"id": p.Id,
"isOn": p.IsOn,
"protectionBandwidthSize": p.ProtectionBandwidthSize,
"protectionBandwidthUnit": p.ProtectionBandwidthUnit,
"serverBandwidthSize": p.ServerBandwidthSize,
"serverBandwidthUnit": p.ServerBandwidthUnit,
"network": networkMap,
"countPrices": countPrices,
"countInstances": countInstances,
})
}
this.Data["packages"] = packageMaps
// 所有线路
networkResp, err := this.RPC().ADNetworkRPC().FindAllADNetworks(this.AdminContext(), &pb.FindAllADNetworkRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var networkMaps = []maps.Map{}
for _, network := range networkResp.AdNetworks {
networkMaps = append(networkMaps, maps.Map{
"id": network.Id,
"name": network.Name,
"isOn": network.IsOn,
})
}
this.Data["networks"] = networkMaps
// 价格项总数量
periodsResp, err := this.RPC().ADPackagePeriodRPC().FindAllAvailableADPackagePeriods(this.AdminContext(), &pb.FindAllAvailableADPackagePeriodsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["totalPriceItems"] = len(periodsResp.AdPackagePeriods)
this.Show()
}

View File

@@ -0,0 +1,73 @@
//go:build plus
package antiddos
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/anti-ddos/instances"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/anti-ddos/instances/instance"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/anti-ddos/networks"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/anti-ddos/networks/network"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/anti-ddos/periods"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/anti-ddos/periods/period"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/anti-ddos/user-instances"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(plus.NewHelper(plus.ComponentCodeAntiDDoS)).
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNode)).
Data("teaMenu", "clusters").
Data("teaSubMenu", "antiDDoS").
// 高防产品
Prefix("/clusters/anti-ddos").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/updatePopup", new(UpdatePopupAction)).
Post("/delete", new(DeleteAction)).
GetPost("/updatePricesPopup", new(UpdatePricesPopupAction)).
Post("/updatePrice", new(UpdatePriceAction)).
// 实例
Prefix("/clusters/anti-ddos/instances").
Get("", new(instances.IndexAction)).
GetPost("/createPopup", new(instances.CreatePopupAction)).
GetPost("/instance/updatePopup", new(instance.UpdatePopupAction)).
Post("/instance/delete", new(instance.DeleteAction)).
// 高防线路
Prefix("/clusters/anti-ddos/networks").
Get("", new(networks.IndexAction)).
GetPost("/createPopup", new(networks.CreatePopupAction)).
GetPost("/network/updatePopup", new(network.UpdatePopupAction)).
Post("/network/delete", new(network.DeleteAction)).
// 高防实例有效期
Prefix("/clusters/anti-ddos/periods").
Get("", new(periods.IndexAction)).
GetPost("/createPopup", new(periods.CreatePopupAction)).
// 高防实例有效期详情
Prefix("/clusters/anti-ddos/periods/period").
GetPost("/updatePopup", new(period.UpdatePopupAction)).
Post("/delete", new(period.DeleteAction)).
// 用户高防实例
Prefix("/clusters/anti-ddos/user-instances").
Get("", new(userinstances.IndexAction)).
GetPost("/createPopup", new(userinstances.CreatePopupAction)).
Post("/price", new(userinstances.PriceAction)).
Post("/delete", new(userinstances.DeleteAction)).
GetPost("/renewPopup", new(userinstances.RenewPopupAction)).
GetPost("/updateObjectsPopup", new(userinstances.UpdateObjectsPopupAction)).
Post("/userServers", new(userinstances.UserServersAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,137 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package instances
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 CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct {
NetworkId int64
PackageId int64
}) {
// 两个参数只能二选一
if params.PackageId > 0 {
params.NetworkId = 0
}
// 产品
this.Data["packageId"] = params.PackageId
this.Data["packageSummary"] = ""
if params.PackageId > 0 {
packageResp, err := this.RPC().ADPackageRPC().FindADPackage(this.AdminContext(), &pb.FindADPackageRequest{AdPackageId: params.PackageId})
if err != nil {
this.ErrorPage(err)
return
}
if packageResp.AdPackage == nil {
this.NotFound("adPackage", params.PackageId)
return
}
this.Data["packageSummary"] = packageResp.AdPackage.Summary
}
// 线路
this.Data["networkId"] = params.NetworkId
networksResp, err := this.RPC().ADNetworkRPC().FindAllAvailableADNetworks(this.AdminContext(), &pb.FindAllAvailableADNetworksRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var networkMaps = []maps.Map{}
for _, network := range networksResp.AdNetworks {
networkMaps = append(networkMaps, maps.Map{
"id": network.Id,
"name": network.Name,
})
}
this.Data["networks"] = networkMaps
// 所有产品
this.Data["packages"] = []maps.Map{}
var packageMaps = []maps.Map{}
var offset int64 = 0
var size int64 = 20
for {
// 这里查询所有线路
packageResp, err := this.RPC().ADPackageRPC().ListADPackages(this.AdminContext(), &pb.ListADPackagesRequest{
Offset: offset,
Size: size,
})
if err != nil {
this.ErrorPage(err)
return
}
if len(packageResp.AdPackages) == 0 {
break
}
for _, p := range packageResp.AdPackages {
packageMaps = append(packageMaps, maps.Map{
"id": p.Id,
"networkId": p.AdNetworkId,
"summary": p.Summary,
})
}
offset += size
}
this.Data["packages"] = packageMaps
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
PackageId int64
ClusterId int64
NodeIds []int64
IpAddresses []string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
var createdInstanceId int64
defer func() {
this.CreateLogInfo(codes.ADPackageInstance_LogCreateADPackageInstance, createdInstanceId)
}()
if params.PackageId <= 0 {
this.Fail("请选择所属高防产品")
return
}
if params.ClusterId <= 0 {
this.Fail("请选择要部署的集群")
return
}
if len(params.IpAddresses) == 0 {
this.Fail("请输入高防IP地址")
return
}
createResp, err := this.RPC().ADPackageInstanceRPC().CreateADPackageInstance(this.AdminContext(), &pb.CreateADPackageInstanceRequest{
AdPackageId: params.PackageId,
NodeClusterId: params.ClusterId,
NodeIds: params.NodeIds,
IpAddresses: params.IpAddresses,
})
if err != nil {
this.ErrorPage(err)
return
}
createdInstanceId = createResp.AdPackageInstanceId
this.Success()
}

View File

@@ -0,0 +1,180 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package instances
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("", "", "instance")
}
func (this *IndexAction) RunGet(params struct {
NetworkId int64
PackageId int64
UserId int64
Ip string
SelectedPackageId int64
}) {
this.Data["networkId"] = params.NetworkId
this.Data["packageId"] = params.PackageId
this.Data["ip"] = params.Ip
this.Data["selectedPackageId"] = params.SelectedPackageId
var networkId = params.NetworkId
var packageId = params.PackageId
var selectedPackageMap = maps.Map{
"id": 0,
"summary": "",
}
if params.SelectedPackageId > 0 {
packageId = params.SelectedPackageId
packageResp, err := this.RPC().ADPackageRPC().FindADPackage(this.AdminContext(), &pb.FindADPackageRequest{
AdPackageId: params.SelectedPackageId,
})
if err != nil {
this.ErrorPage(err)
return
}
var adPackage = packageResp.AdPackage
if adPackage == nil {
this.NotFound("adPackage", params.SelectedPackageId)
return
}
selectedPackageMap = maps.Map{
"id": adPackage.Id,
"summary": adPackage.Summary,
}
}
this.Data["selectedPackage"] = selectedPackageMap
countResp, err := this.RPC().ADPackageInstanceRPC().CountADPackageInstances(this.AdminContext(), &pb.CountADPackageInstancesRequest{
AdNetworkId: networkId,
AdPackageId: packageId,
UserId: params.UserId,
Ip: params.Ip,
})
if err != nil {
this.ErrorPage(err)
return
}
var page = this.NewPage(countResp.Count)
this.Data["page"] = page.AsHTML()
listResp, err := this.RPC().ADPackageInstanceRPC().ListADPackageInstances(this.AdminContext(), &pb.ListADPackageInstancesRequest{
AdNetworkId: networkId,
AdPackageId: packageId,
UserId: params.UserId,
Ip: params.Ip,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var instanceMaps = []maps.Map{}
for _, instance := range listResp.AdPackageInstances {
// network
var networkMap = maps.Map{
"id": 0,
"name": "",
}
// package
var packageMap = maps.Map{
"id": 0,
}
if instance.AdPackage != nil {
packageMap = maps.Map{
"id": instance.AdPackage.Id,
"protectionBandwidthSize": instance.AdPackage.ProtectionBandwidthSize,
"protectionBandwidthUnit": instance.AdPackage.ProtectionBandwidthUnit,
"serverBandwidthSize": instance.AdPackage.ServerBandwidthSize,
"serverBandwidthUnit": instance.AdPackage.ServerBandwidthUnit,
}
if instance.AdPackage.AdNetwork != nil {
networkMap = maps.Map{
"id": instance.AdPackage.AdNetwork.Id,
"name": instance.AdPackage.AdNetwork.Name,
}
}
}
// 集群
var clusterMap = maps.Map{
"id": 0,
"name": "",
}
if instance.NodeCluster != nil {
clusterMap = maps.Map{
"id": instance.NodeCluster.Id,
"name": instance.NodeCluster.Name,
}
}
// IP地址
if instance.IpAddresses == nil {
instance.IpAddresses = []string{}
}
// user
var userMap = maps.Map{
"id": 0,
}
if instance.User != nil {
userMap = maps.Map{
"id": instance.User.Id,
"fullname": instance.User.Fullname,
"username": instance.User.Username,
}
}
var userDayTo = instance.UserDayTo
if len(userDayTo) == 8 {
userDayTo = userDayTo[:4] + "-" + userDayTo[4:6] + "-" + userDayTo[6:]
}
instanceMaps = append(instanceMaps, maps.Map{
"id": instance.Id,
"isOn": instance.IsOn,
"network": networkMap,
"package": packageMap,
"cluster": clusterMap,
"ipAddresses": instance.IpAddresses,
"userDayTo": userDayTo,
"user": userMap,
})
}
this.Data["instances"] = instanceMaps
// 所有线路
var networkMaps = []maps.Map{}
networksResp, err := this.RPC().ADNetworkRPC().FindAllAvailableADNetworks(this.AdminContext(), &pb.FindAllAvailableADNetworksRequest{})
if err != nil {
this.ErrorPage(err)
return
}
for _, network := range networksResp.AdNetworks {
networkMaps = append(networkMaps, maps.Map{
"id": network.Id,
"name": network.Name,
})
}
this.Data["networks"] = networkMaps
this.Show()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package instance
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 {
InstanceId int64
}) {
defer this.CreateLogInfo(codes.ADPackageInstance_LogDeleteADPackageInstance, params.InstanceId)
_, err := this.RPC().ADPackageInstanceRPC().DeleteADPackageInstance(this.AdminContext(), &pb.DeleteADPackageInstanceRequest{AdPackageInstanceId: params.InstanceId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,119 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package instance
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 UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
InstanceId int64
}) {
this.Data["instanceId"] = params.InstanceId
instanceResp, err := this.RPC().ADPackageInstanceRPC().FindADPackageInstance(this.AdminContext(), &pb.FindADPackageInstanceRequest{AdPackageInstanceId: params.InstanceId})
if err != nil {
this.ErrorPage(err)
return
}
var instance = instanceResp.AdPackageInstance
if instance == nil {
this.NotFound("adPackageInstance", params.InstanceId)
return
}
var ipAddresses = instance.IpAddresses
if ipAddresses == nil {
ipAddresses = []string{}
}
// network
var networkMap = maps.Map{
"id": 0,
"name": "",
}
// package
var packageMap = maps.Map{
"id": 0,
"summary": "",
}
if instance.AdPackage != nil {
packageMap = maps.Map{
"id": instance.AdPackage.Id,
"protectionBandwidthSize": instance.AdPackage.ProtectionBandwidthSize,
"protectionBandwidthUnit": instance.AdPackage.ProtectionBandwidthUnit,
"serverBandwidthSize": instance.AdPackage.ServerBandwidthSize,
"serverBandwidthUnit": instance.AdPackage.ServerBandwidthUnit,
"summary": instance.AdPackage.Summary,
}
if instance.AdPackage.AdNetwork != nil {
networkMap = maps.Map{
"id": instance.AdPackage.AdNetwork.Id,
"name": instance.AdPackage.AdNetwork.Name,
}
}
}
this.Data["instance"] = maps.Map{
"id": instance.Id,
"clusterId": instance.NodeClusterId,
"ipAddresses": ipAddresses,
"isOn": instance.IsOn,
"network": networkMap,
"package": packageMap,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
InstanceId int64
ClusterId int64
NodeIds []int64
IpAddresses []string
IsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.ADPackageInstance_LogUpdateADPackageInstance, params.InstanceId)
if params.ClusterId <= 0 {
this.Fail("请选择要部署的集群")
return
}
if len(params.IpAddresses) == 0 {
this.Fail("请输入高防IP地址")
return
}
_, err := this.RPC().ADPackageInstanceRPC().UpdateADPackageInstance(this.AdminContext(), &pb.UpdateADPackageInstanceRequest{
AdPackageInstanceId: params.InstanceId,
NodeClusterId: params.ClusterId,
NodeIds: params.NodeIds,
IpAddresses: params.IpAddresses,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,52 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package networks
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 {
Name string
Description string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
var networkId int64
defer func() {
this.CreateLogInfo(codes.ADNetwork_LogCreateADNetwork, networkId)
}()
params.Must.
Field("name", params.Name).
Require("请输入线路名称")
createResp, err := this.RPC().ADNetworkRPC().CreateADNetwork(this.AdminContext(), &pb.CreateADNetworkRequest{
Name: params.Name,
Description: params.Description,
})
if err != nil {
this.ErrorPage(err)
return
}
networkId = createResp.AdNetworkId
this.Success()
}

View File

@@ -0,0 +1,38 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package networks
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("", "", "network")
}
func (this *IndexAction) RunGet(params struct{}) {
networksResp, err := this.RPC().ADNetworkRPC().FindAllADNetworks(this.AdminContext(), &pb.FindAllADNetworkRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var networkMaps = []maps.Map{}
for _, network := range networksResp.AdNetworks {
networkMaps = append(networkMaps, maps.Map{
"id": network.Id,
"name": network.Name,
"description": network.Description,
"isOn": network.IsOn,
})
}
this.Data["networks"] = networkMaps
this.Show()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package network
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 {
NetworkId int64
}) {
defer this.CreateLogInfo(codes.ADNetwork_LogDeleteADNetwork)
_, err := this.RPC().ADNetworkRPC().DeleteADNetwork(this.AdminContext(), &pb.DeleteADNetworkRequest{AdNetworkId: params.NetworkId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,72 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package network
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 UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
NetworkId int64
}) {
networkResp, err := this.RPC().ADNetworkRPC().FindADNetwork(this.AdminContext(), &pb.FindADNetworkRequest{AdNetworkId: params.NetworkId})
if err != nil {
this.ErrorPage(err)
return
}
var network = networkResp.AdNetwork
if network == nil {
this.NotFound("adNetwork", params.NetworkId)
return
}
this.Data["network"] = maps.Map{
"id": network.Id,
"name": network.Name,
"description": network.Description,
"isOn": network.IsOn,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
NetworkId int64
Name string
Description string
IsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.ADNetwork_LogUpdateADNetwork, params.NetworkId)
params.Must.
Field("name", params.Name).
Require("请输入线路名称")
_, err := this.RPC().ADNetworkRPC().UpdateADNetwork(this.AdminContext(), &pb.UpdateADNetworkRequest{
AdNetworkId: params.NetworkId,
IsOn: params.IsOn,
Name: params.Name,
Description: params.Description,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,53 @@
// Copyright 2023 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.ADPackagePeriod_LogCreateADPackagePeriod, periodId)
}()
if params.Count <= 0 {
this.FailField("count", "请输入有效期数量")
}
createResp, err := this.RPC().ADPackagePeriodRPC().CreateADPackagePeriod(this.AdminContext(), &pb.CreateADPackagePeriodRequest{
Count: params.Count,
Unit: params.Unit,
})
if err != nil {
this.ErrorPage(err)
return
}
periodId = createResp.AdPackagePeriodId
this.Success()
}

View File

@@ -0,0 +1,40 @@
// Copyright 2023 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/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{}) {
// 所有有效期
periodsResp, err := this.RPC().ADPackagePeriodRPC().FindAllADPackagePeriods(this.AdminContext(), &pb.FindAllADPackagePeriodsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var periodMaps = []maps.Map{}
for _, period := range periodsResp.AdPackagePeriods {
periodMaps = append(periodMaps, maps.Map{
"id": period.Id,
"count": period.Count,
"unit": period.Unit,
"unitName": userconfigs.ADPackagePeriodUnitName(period.Unit),
"isOn": period.IsOn,
})
}
this.Data["periods"] = periodMaps
this.Show()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2023 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.ADPackagePeriod_LogDeleteADPackagePeriod, params.PeriodId)
_, err := this.RPC().ADPackagePeriodRPC().DeleteADPackagePeriod(this.AdminContext(), &pb.DeleteADPackagePeriodRequest{AdPackagePeriodId: params.PeriodId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,64 @@
// Copyright 2023 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().ADPackagePeriodRPC().FindADPackagePeriod(this.AdminContext(), &pb.FindADPackagePeriodRequest{AdPackagePeriodId: params.PeriodId})
if err != nil {
this.ErrorPage(err)
return
}
var period = periodResp.AdPackagePeriod
if period == nil {
this.NotFound("adPackagePeriod", 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.ADPackagePeriod_LogUpdateADPackagePeriod, params.PeriodId)
_, err := this.RPC().ADPackagePeriodRPC().UpdateADPackagePeriod(this.AdminContext(), &pb.UpdateADPackagePeriodRequest{
AdPackagePeriodId: params.PeriodId,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,137 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package antiddos
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 {
PackageId int64
}) {
packageResp, err := this.RPC().ADPackageRPC().FindADPackage(this.AdminContext(), &pb.FindADPackageRequest{AdPackageId: params.PackageId})
if err != nil {
this.ErrorPage(err)
return
}
var adPackage = packageResp.AdPackage
if adPackage == nil {
this.NotFound("adPackage", params.PackageId)
return
}
this.Data["package"] = maps.Map{
"id": adPackage.Id,
"isOn": adPackage.IsOn,
"protectionBandwidthSize": adPackage.ProtectionBandwidthSize,
"protectionBandwidthUnit": adPackage.ProtectionBandwidthUnit,
"serverBandwidthSize": adPackage.ServerBandwidthSize,
"serverBandwidthUnit": adPackage.ServerBandwidthUnit,
"networkId": adPackage.AdNetworkId,
}
// 线路选项
networkResp, err := this.RPC().ADNetworkRPC().FindAllADNetworks(this.AdminContext(), &pb.FindAllADNetworkRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var networkMaps = []maps.Map{}
for _, network := range networkResp.AdNetworks {
networkMaps = append(networkMaps, maps.Map{
"id": network.Id,
"name": network.Name,
"isOn": network.IsOn,
})
}
this.Data["networks"] = networkMaps
// 带宽单位
this.Data["protectionUnitOptions"] = []maps.Map{
{
"code": userconfigs.ADPackageSizeUnitGb,
"name": "Gbps",
},
{
"code": userconfigs.ADPackageSizeUnitTb,
"name": "Tbps",
},
}
this.Data["serverUnitOptions"] = []maps.Map{
{
"code": userconfigs.ADPackageSizeUnitMb,
"name": "Mbps",
},
{
"code": userconfigs.ADPackageSizeUnitGb,
"name": "Gbps",
},
{
"code": userconfigs.ADPackageSizeUnitTb,
"name": "Tbps",
},
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
PackageId int64
NetworkId int64
ProtectionBandwidthSize int32
ProtectionBandwidthUnit string
ServerBandwidthSize int32
ServerBandwidthUnit string
IsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.ADPackage_LogUpdateADPackage, params.PackageId)
if params.PackageId <= 0 {
this.Fail("请选择要修改的高防产品")
return
}
if params.NetworkId <= 0 {
this.Fail("请选择所属线路")
return
}
if params.ProtectionBandwidthSize <= 0 || len(params.ProtectionBandwidthUnit) == 0 {
this.Fail("请输入防护带宽")
return
}
if params.ServerBandwidthSize <= 0 || len(params.ServerBandwidthUnit) == 0 {
this.Fail("请输入业务带宽")
return
}
_, err := this.RPC().ADPackageRPC().UpdateADPackage(this.AdminContext(), &pb.UpdateADPackageRequest{
AdPackageId: params.PackageId,
AdNetworkId: params.NetworkId,
ProtectionBandwidthSize: params.ProtectionBandwidthSize,
ProtectionBandwidthUnit: params.ProtectionBandwidthUnit,
ServerBandwidthSize: params.ServerBandwidthSize,
ServerBandwidthUnit: params.ServerBandwidthUnit,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,37 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package antiddos
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) Init() {
this.Nav("", "", "")
}
func (this *UpdatePriceAction) RunPost(params struct {
PackageId int64
PeriodId int64
Price float64
}) {
defer this.CreateLogInfo(codes.ADPackagePrice_LogUpdateADPackagePrice, params.PackageId, params.PeriodId)
_, err := this.RPC().ADPackagePriceRPC().UpdateADPackagePrice(this.AdminContext(), &pb.UpdateADPackagePriceRequest{
AdPackageId: params.PackageId,
AdPackagePeriodId: params.PeriodId,
Price: params.Price,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,75 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package antiddos
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"
)
type UpdatePricesPopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePricesPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePricesPopupAction) RunGet(params struct {
PackageId int64
}) {
// 高防产品信息
packageResp, err := this.RPC().ADPackageRPC().FindADPackage(this.AdminContext(), &pb.FindADPackageRequest{AdPackageId: params.PackageId})
if err != nil {
this.ErrorPage(err)
return
}
var p = packageResp.AdPackage
if p == nil {
this.NotFound("adPackage", params.PackageId)
return
}
this.Data["package"] = maps.Map{
"id": p.Id,
"protectionBandwidthSize": p.ProtectionBandwidthSize,
"protectionBandwidthUnit": p.ProtectionBandwidthUnit,
"serverBandwidthSize": p.ServerBandwidthSize,
"serverBandwidthUnit": p.ServerBandwidthUnit,
}
// 有效期选项
periodsResp, err := this.RPC().ADPackagePeriodRPC().FindAllAvailableADPackagePeriods(this.AdminContext(), &pb.FindAllAvailableADPackagePeriodsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var periodMaps = []maps.Map{}
for _, period := range periodsResp.AdPackagePeriods {
periodMaps = append(periodMaps, maps.Map{
"id": period.Id,
"count": period.Count,
"unit": period.Unit,
"name": types.String(period.Count) + userconfigs.ADPackagePeriodUnitName(period.Unit),
})
}
this.Data["periods"] = periodMaps
// 所有价格
pricesResp, err := this.RPC().ADPackagePriceRPC().FindADPackagePrices(this.AdminContext(), &pb.FindADPackagePricesRequest{AdPackageId: params.PackageId})
if err != nil {
this.ErrorPage(err)
return
}
var priceMap = map[string]float64{} // periodIdString => price
for _, price := range pricesResp.AdPackagePrices {
priceMap[types.String(price.AdPackagePeriodId)] = price.Price
}
this.Data["prices"] = priceMap
this.Show()
}

View File

@@ -0,0 +1,207 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package userinstances
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/lists"
"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{}) {
// 高防产品
var allPackageMap = map[int64]*pb.ADPackage{} // packageId => *pb.ADPackage
packagesResp, err := this.RPC().ADPackageRPC().FindAllIdleADPackages(this.AdminContext(), &pb.FindAllIdleADPackagesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
for _, p := range packagesResp.AdPackages {
allPackageMap[p.Id] = p
}
// 价格
pricesResp, err := this.RPC().ADPackagePriceRPC().FindAllADPackagePrices(this.AdminContext(), &pb.FindAllADPackagePricesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var priceMaps = []maps.Map{}
for _, price := range pricesResp.AdPackagePrices {
if price.Price > 0 {
var packageId = price.AdPackageId
var periodId = price.AdPackagePeriodId
p, ok := allPackageMap[packageId]
if !ok {
continue
}
var networkId = p.AdNetworkId
var countIdleInstances = p.CountIdleADPackageInstances
if packageId > 0 && periodId > 0 && networkId > 0 && countIdleInstances > 0 {
priceMaps = append(priceMaps, maps.Map{
"networkId": networkId,
"packageId": packageId,
"protectionBandwidth": types.String(p.ProtectionBandwidthSize) + userconfigs.ADPackageSizeFullUnit(p.ProtectionBandwidthUnit),
"serverBandwidth": types.String(p.ServerBandwidthSize) + userconfigs.ADPackageSizeFullUnit(p.ServerBandwidthUnit),
"periodId": periodId,
"price": price.Price,
"maxInstances": countIdleInstances,
})
}
}
}
this.Data["prices"] = priceMaps
// 重新处理防护带宽和服务带宽
var allProtectionBandwidthSizes = []string{}
var allServerBandwidthSizes = []string{}
for _, p := range packagesResp.AdPackages {
var protectionSize = types.String(p.ProtectionBandwidthSize) + userconfigs.ADPackageSizeFullUnit(p.ProtectionBandwidthUnit)
var serverSize = types.String(p.ServerBandwidthSize) + userconfigs.ADPackageSizeFullUnit(p.ServerBandwidthUnit)
if !lists.ContainsString(allProtectionBandwidthSizes, protectionSize) {
var found = false
for _, price := range priceMaps {
if types.String(price["protectionBandwidth"]) == protectionSize {
found = true
}
}
if found {
allProtectionBandwidthSizes = append(allProtectionBandwidthSizes, protectionSize)
}
}
if !lists.ContainsString(allServerBandwidthSizes, serverSize) {
var found = false
for _, price := range priceMaps {
if types.String(price["serverBandwidth"]) == serverSize {
found = true
}
}
if found {
allServerBandwidthSizes = append(allServerBandwidthSizes, serverSize)
}
}
}
this.Data["allProtectionBandwidthSizes"] = allProtectionBandwidthSizes
this.Data["allServerBandwidthSizes"] = allServerBandwidthSizes
// 线路
var networkMaps = []maps.Map{}
networkResp, err := this.RPC().ADNetworkRPC().FindAllAvailableADNetworks(this.AdminContext(), &pb.FindAllAvailableADNetworksRequest{})
if err != nil {
this.ErrorPage(err)
return
}
for _, network := range networkResp.AdNetworks {
var found = false
for _, price := range priceMaps {
if types.Int64(price["networkId"]) == network.Id {
found = true
}
}
if found {
networkMaps = append(networkMaps, maps.Map{
"id": network.Id,
"name": network.Name,
"description": network.Description,
})
}
}
this.Data["allNetworks"] = networkMaps
// 周期
var periodMaps = []maps.Map{}
periodResp, err := this.RPC().ADPackagePeriodRPC().FindAllAvailableADPackagePeriods(this.AdminContext(), &pb.FindAllAvailableADPackagePeriodsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
for _, period := range periodResp.AdPackagePeriods {
var found = false
for _, price := range priceMaps {
if types.Int64(price["periodId"]) == period.Id {
found = true
}
}
if found {
periodMaps = append(periodMaps, maps.Map{
"id": period.Id,
"count": period.Count,
"unit": period.Unit,
"unitName": userconfigs.ADPackagePeriodUnitName(period.Unit),
})
}
}
this.Data["allPeriods"] = periodMaps
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
UserId int64
PackageId int64
PeriodId int64
Count int32
}) {
defer this.CreateLogInfo(codes.ADPackagePrice_LogCreateADPackagePrice, params.UserId, params.PackageId, params.PeriodId, params.Count)
if params.UserId <= 0 {
this.Fail("请选择用户")
return
}
if params.PackageId <= 0 {
this.Fail("请选择高防产品")
return
}
if params.PeriodId <= 0 {
this.Fail("请选择有效期")
return
}
if params.Count <= 0 {
this.Fail("请选择实例数量")
return
}
resp, err := this.RPC().ADPackagePriceRPC().FindADPackagePrice(this.AdminContext(), &pb.FindADPackagePriceRequest{
AdPackageId: params.PackageId,
AdPackagePeriodId: params.PeriodId,
Count: params.Count,
})
if err != nil {
this.ErrorPage(err)
return
}
if resp.Price == 0 {
this.Fail("当前所选条件下的高防实例不可用")
return
}
_, err = this.RPC().UserADInstanceRPC().CreateUserADInstance(this.AdminContext(), &pb.CreateUserADInstanceRequest{
UserId: params.UserId,
AdPackageId: params.PackageId,
AdPackagePeriodId: 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 userinstances
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 {
UserInstanceId int64
}) {
defer this.CreateLogInfo(codes.UserADInstance_LogDeleteUserADInstance, params.UserInstanceId)
_, err := this.RPC().UserADInstanceRPC().DeleteUserADInstance(this.AdminContext(), &pb.DeleteUserADInstanceRequest{UserADInstanceId: params.UserInstanceId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,118 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package userinstances
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/dateutils"
"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
NetworkId int64
PeriodId int64
}) {
countResp, err := this.RPC().UserADInstanceRPC().CountUserADInstances(this.AdminContext(), &pb.CountUserADInstancesRequest{
AdNetworkId: params.NetworkId,
UserId: params.UserId,
ExpiresDay: "",
AdPackagePeriodId: params.PeriodId,
})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
var page = this.NewPage(count)
this.Data["page"] = page.AsHTML()
userInstancesResp, err := this.RPC().UserADInstanceRPC().ListUserADInstances(this.AdminContext(), &pb.ListUserADInstancesRequest{
AdNetworkId: params.NetworkId,
UserId: params.UserId,
AdPackagePeriodId: params.PeriodId,
ExpiresDay: "",
AvailableOnly: false,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var userInstanceMaps = []maps.Map{}
for _, userInstance := range userInstancesResp.UserADInstances {
// 用户
var userMap = maps.Map{
"id": 0,
}
if userInstance.User != nil {
userMap = maps.Map{
"id": userInstance.User.Id,
"fullname": userInstance.User.Fullname,
"username": userInstance.User.Username,
}
}
// 实例
var instanceMap = maps.Map{
"id": 0,
"userInstanceId": 0,
}
if userInstance.AdPackageInstance != nil {
instanceMap = maps.Map{
"id": userInstance.AdPackageInstance.Id,
"userInstanceId": userInstance.AdPackageInstance.UserInstanceId,
}
}
// 产品
var packageMap = maps.Map{
"id": 0,
}
if userInstance.AdPackageInstance != nil && userInstance.AdPackageInstance.AdPackage != nil {
packageMap = maps.Map{
"id": userInstance.AdPackageInstance.AdPackage.Id,
"summary": userInstance.AdPackageInstance.AdPackage.Summary,
}
}
// 实例
var ipAddresses = []string{}
if userInstance.AdPackageInstance != nil && len(userInstance.AdPackageInstance.IpAddresses) > 0 {
ipAddresses = userInstance.AdPackageInstance.IpAddresses
}
userInstanceMaps = append(userInstanceMaps, maps.Map{
"id": userInstance.Id,
"dayFrom": dateutils.SplitYmd(userInstance.DayFrom),
"dayTo": dateutils.SplitYmd(userInstance.DayTo),
"user": userMap,
"instance": instanceMap,
"package": packageMap,
"ipAddresses": ipAddresses,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", userInstance.CreatedAt),
"periodCount": userInstance.AdPackagePeriodCount,
"periodUnitName": userconfigs.ADPackagePeriodUnitName(userInstance.AdPackagePeriodUnit),
"canDelete": userInstance.CanDelete,
"isExpired": userInstance.DayTo < timeutil.Format("Ymd"),
"isAvailable": userInstance.IsAvailable,
"countObjects": userInstance.CountObjects,
})
}
this.Data["userInstances"] = userInstanceMaps
this.Show()
}

View File

@@ -0,0 +1,54 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package userinstances
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/types"
)
type PriceAction struct {
actionutils.ParentAction
}
func (this *PriceAction) RunPost(params struct {
PackageId int64
PeriodId int64
Count int32
}) {
if params.PackageId <= 0 || params.PeriodId <= 0 || params.Count <= 0 {
this.Data["price"] = 0
this.Data["amount"] = 0
this.Success()
return
}
// 数量
countInstancesResp, err := this.RPC().ADPackageInstanceRPC().CountIdleADPackageInstances(this.AdminContext(), &pb.CountIdleADPackageInstancesRequest{AdPackageId: params.PackageId})
if err != nil {
this.ErrorPage(err)
return
}
var countInstances = types.Int32(countInstancesResp.Count)
if countInstances < params.Count {
this.Data["price"] = 0
this.Data["amount"] = 0
this.Success()
return
}
resp, err := this.RPC().ADPackagePriceRPC().FindADPackagePrice(this.AdminContext(), &pb.FindADPackagePriceRequest{
AdPackageId: params.PackageId,
AdPackagePeriodId: 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,154 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package userinstances
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/dateutils"
"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/lists"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type RenewPopupAction struct {
actionutils.ParentAction
}
func (this *RenewPopupAction) Init() {
this.Nav("", "", "")
}
func (this *RenewPopupAction) RunGet(params struct {
UserInstanceId int64
}) {
userInstanceResp, err := this.RPC().UserADInstanceRPC().FindUserADInstance(this.AdminContext(), &pb.FindUserADInstanceRequest{UserADInstanceId: params.UserInstanceId})
if err != nil {
this.ErrorPage(err)
return
}
var userInstance = userInstanceResp.UserADInstance
if userInstance == nil {
this.NotFound("userInstance", params.UserInstanceId)
return
}
// 用户
var userMap = maps.Map{
"id": 0,
}
if userInstance.User != nil {
userMap = maps.Map{
"id": userInstance.User.Id,
"fullname": userInstance.User.Fullname,
"username": userInstance.User.Username,
}
}
// 实例
var instanceMap = maps.Map{
"id": 0,
}
if userInstance.AdPackageInstance != nil {
if userInstance.AdPackageInstance.IpAddresses == nil {
userInstance.AdPackageInstance.IpAddresses = []string{}
}
instanceMap = maps.Map{
"id": userInstance.AdPackageInstance.Id,
"ipAddresses": userInstance.AdPackageInstance.IpAddresses,
}
}
// 产品
var packageMap = maps.Map{
"id": 0,
}
var packageId int64
if userInstance.AdPackageInstance != nil && userInstance.AdPackageInstance.AdPackage != nil {
packageId = userInstance.AdPackageInstance.AdPackage.Id
packageMap = maps.Map{
"id": userInstance.AdPackageInstance.AdPackage.Id,
"summary": userInstance.AdPackageInstance.AdPackage.Summary,
}
}
this.Data["userInstance"] = maps.Map{
"id": userInstance.Id,
"periodId": userInstance.AdPackagePeriodId,
"dayTo": dateutils.SplitYmd(userInstance.DayTo),
"today": timeutil.Format("Y-m-d"),
"isExpired": len(userInstance.DayTo) == 0 || userInstance.DayTo < timeutil.Format("Ymd"),
"isAvailable": userInstance.IsAvailable,
"user": userMap,
"instance": instanceMap,
"package": packageMap,
}
// 价格选项
if packageId > 0 {
pricesResp, err := this.RPC().ADPackagePriceRPC().FindADPackagePrices(this.AdminContext(), &pb.FindADPackagePricesRequest{AdPackageId: packageId})
if err != nil {
this.ErrorPage(err)
return
}
var priceMaps = []maps.Map{}
var allValidPeriodIds = []int64{}
for _, price := range pricesResp.AdPackagePrices {
allValidPeriodIds = append(allValidPeriodIds, price.AdPackagePeriodId)
priceMaps = append(priceMaps, maps.Map{
"periodId": price.AdPackagePeriodId,
"price": price.Price,
})
}
this.Data["prices"] = priceMaps
// 有效期选项
var periodMaps = []maps.Map{}
periodResp, err := this.RPC().ADPackagePeriodRPC().FindAllAvailableADPackagePeriods(this.AdminContext(), &pb.FindAllAvailableADPackagePeriodsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
for _, period := range periodResp.AdPackagePeriods {
if !lists.ContainsInt64(allValidPeriodIds, period.Id) {
continue
}
periodMaps = append(periodMaps, maps.Map{
"id": period.Id,
"count": period.Count,
"unit": period.Unit,
"unitName": userconfigs.ADPackagePeriodUnitName(period.Unit),
})
}
this.Data["allPeriods"] = periodMaps
} else {
this.Data["allPeriods"] = []maps.Map{}
this.Data["prices"] = []maps.Map{}
}
this.Show()
}
func (this *RenewPopupAction) RunPost(params struct {
UserInstanceId int64
PeriodId int64
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.UserADInstance_LogRenewUserADInstance, params.UserInstanceId)
_, err := this.RPC().UserADInstanceRPC().RenewUserADInstance(this.AdminContext(), &pb.RenewUserADInstanceRequest{
UserADInstanceId: params.UserInstanceId,
AdPackagePeriodId: params.PeriodId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,129 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package userinstances
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/dateutils"
"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"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type UpdateObjectsPopupAction struct {
actionutils.ParentAction
}
func (this *UpdateObjectsPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdateObjectsPopupAction) RunGet(params struct {
UserInstanceId int64
}) {
userInstanceResp, err := this.RPC().UserADInstanceRPC().FindUserADInstance(this.AdminContext(), &pb.FindUserADInstanceRequest{UserADInstanceId: params.UserInstanceId})
if err != nil {
this.ErrorPage(err)
return
}
var userInstance = userInstanceResp.UserADInstance
if userInstance == nil {
this.NotFound("userInstance", params.UserInstanceId)
return
}
var objectMaps = []maps.Map{}
if len(userInstance.ObjectsJSON) > 0 {
err = json.Unmarshal(userInstance.ObjectsJSON, &objectMaps)
if err != nil {
this.ErrorPage(err)
return
}
}
// 用户
var userMap = maps.Map{
"id": 0,
}
if userInstance.User != nil {
userMap = maps.Map{
"id": userInstance.User.Id,
"fullname": userInstance.User.Fullname,
"username": userInstance.User.Username,
}
}
// 实例
var instanceMap = maps.Map{
"id": 0,
}
if userInstance.AdPackageInstance != nil {
if userInstance.AdPackageInstance.IpAddresses == nil {
userInstance.AdPackageInstance.IpAddresses = []string{}
}
instanceMap = maps.Map{
"id": userInstance.AdPackageInstance.Id,
"ipAddresses": userInstance.AdPackageInstance.IpAddresses,
}
}
// 产品
var packageMap = maps.Map{
"id": 0,
}
if userInstance.AdPackageInstance != nil && userInstance.AdPackageInstance.AdPackage != nil {
packageMap = maps.Map{
"id": userInstance.AdPackageInstance.AdPackage.Id,
"summary": userInstance.AdPackageInstance.AdPackage.Summary,
}
}
this.Data["userInstance"] = maps.Map{
"id": userInstance.Id,
"periodId": userInstance.AdPackagePeriodId,
"isAvailable": userInstance.IsAvailable,
"objects": objectMaps,
"dayTo": dateutils.SplitYmd(userInstance.DayTo),
"today": timeutil.Format("Y-m-d"),
"isExpired": len(userInstance.DayTo) == 0 || userInstance.DayTo < timeutil.Format("Ymd"),
"user": userMap,
"instance": instanceMap,
"package": packageMap,
}
this.Show()
}
func (this *UpdateObjectsPopupAction) RunPost(params struct {
UserInstanceId int64
ObjectCodesJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo(codes.UserADInstance_LogUpdateUserADInstanceObjects, params.UserInstanceId)
var objectCodes = []string{}
if len(params.ObjectCodesJSON) > 0 {
err := json.Unmarshal(params.ObjectCodesJSON, &objectCodes)
if err != nil {
this.ErrorPage(err)
return
}
}
// TODO 检查有没有超出最大防护对象数量
_, err := this.RPC().UserADInstanceRPC().UpdateUserADInstanceObjects(this.AdminContext(), &pb.UpdateUserADInstanceObjectsRequest{
UserADInstanceId: params.UserInstanceId,
ObjectCodes: objectCodes,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,62 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package userinstances
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type UserServersAction struct {
actionutils.ParentAction
}
func (this *UserServersAction) Init() {
this.Nav("", "", "")
}
func (this *UserServersAction) RunPost(params struct {
UserId int64
Page int32
}) {
var size int64 = 10
// 数量
countResp, err := this.RPC().ServerRPC().CountAllEnabledServersMatch(this.AdminContext(), &pb.CountAllEnabledServersMatchRequest{
UserId: params.UserId,
})
if err != nil {
this.ErrorPage(err)
return
}
var count = countResp.Count
var page = this.NewPage(count, size)
// 列表
serversResp, err := this.RPC().ServerRPC().ListEnabledServersMatch(this.AdminContext(), &pb.ListEnabledServersMatchRequest{
Offset: page.Offset,
Size: page.Size,
UserId: params.UserId,
IgnoreServerNames: true,
IgnoreSSLCerts: true,
})
if err != nil {
this.ErrorPage(err)
return
}
var serverMaps = []maps.Map{}
for _, server := range serversResp.Servers {
serverMaps = append(serverMaps, maps.Map{
"id": server.Id,
"name": server.Name,
})
}
this.Data["servers"] = serverMaps
this.Data["page"] = maps.Map{
"max": page.Max,
}
this.Success()
}

View File

@@ -0,0 +1,52 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package boards
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 DomainStatsAction struct {
actionutils.ParentAction
}
func (this *DomainStatsAction) RunPost(params struct {
ClusterId int64
}) {
var hourFrom = timeutil.Format("YmdH", time.Now().Add(-23*time.Hour))
var hourTo = timeutil.Format("YmdH")
resp, err := this.RPC().ServerDomainHourlyStatRPC().ListTopServerDomainStatsWithServerId(this.AdminContext(), &pb.ListTopServerDomainStatsWithServerIdRequest{
NodeClusterId: params.ClusterId,
HourFrom: hourFrom,
HourTo: hourTo,
Size: 10,
})
if err != nil {
this.ErrorPage(err)
return
}
// 域名排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.DomainStats {
statMaps = append(statMaps, maps.Map{
"serverId": stat.ServerId,
"domain": stat.Domain,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
})
}
this.Data["topDomainStats"] = statMaps
}
this.Success()
}

View File

@@ -0,0 +1,200 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package boards
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"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/serverconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "board", "")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
this.Data["clusterId"] = params.ClusterId
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
}) {
if !teaconst.IsPlus {
this.Fail("only for commercial users")
}
resp, err := this.RPC().ServerStatBoardRPC().ComposeServerStatNodeClusterBoard(this.AdminContext(), &pb.ComposeServerStatNodeClusterBoardRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["board"] = maps.Map{
"countUsers": resp.CountUsers,
"countActiveNodes": resp.CountActiveNodes,
"countInactiveNodes": resp.CountInactiveNodes,
"countServers": resp.CountServers,
}
// 24小时流量趋势
{
var statMaps = []maps.Map{}
for _, stat := range resp.HourlyTrafficStats {
statMaps = append(statMaps, maps.Map{
"bytes": stat.Bytes,
"cachedBytes": stat.CachedBytes,
"countRequests": stat.CountRequests,
"countCachedRequests": stat.CountCachedRequests,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
"day": stat.Hour[4:6] + "月" + stat.Hour[6:8] + "日",
"hour": stat.Hour[8:],
})
}
this.Data["hourlyStats"] = statMaps
}
// 15天流量趋势
{
var statMaps = []maps.Map{}
for _, stat := range resp.DailyTrafficStats {
statMaps = append(statMaps, maps.Map{
"bytes": stat.Bytes,
"cachedBytes": stat.CachedBytes,
"countRequests": stat.CountRequests,
"countCachedRequests": stat.CountCachedRequests,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
})
}
this.Data["dailyStats"] = statMaps
}
// 当月流量
this.Data["monthlyTraffic"] = numberutils.FormatBytes(resp.MonthlyTrafficBytes)
// 今日流量
this.Data["todayTraffic"] = numberutils.FormatBytes(resp.DailyTrafficBytes)
// 昨日流量
this.Data["yesterdayTraffic"] = numberutils.FormatBytes(resp.LastDailyTrafficBytes)
// 节点排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.TopNodeStats {
statMaps = append(statMaps, maps.Map{
"nodeId": stat.NodeId,
"nodeName": stat.NodeName,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
})
}
this.Data["topNodeStats"] = statMaps
}
// CPU
{
var statMaps = []maps.Map{}
for _, stat := range resp.CpuNodeValues {
var valueMap = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &valueMap)
if err != nil {
continue
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": valueMap.GetFloat32("usage"),
})
}
this.Data["cpuValues"] = statMaps
}
// Memory
{
var statMaps = []maps.Map{}
for _, stat := range resp.MemoryNodeValues {
var valueMap = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &valueMap)
if err != nil {
continue
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": valueMap.GetFloat32("usage"),
})
}
this.Data["memoryValues"] = statMaps
}
// Load
{
var statMaps = []maps.Map{}
for _, stat := range resp.LoadNodeValues {
var valueMap = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &valueMap)
if err != nil {
continue
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": valueMap.GetFloat32("load1m"),
})
}
this.Data["loadValues"] = statMaps
}
// 指标
{
var chartMaps = []maps.Map{}
for _, chart := range resp.MetricDataCharts {
var statMaps = []maps.Map{}
for _, stat := range chart.MetricStats {
statMaps = append(statMaps, maps.Map{
"keys": stat.Keys,
"time": stat.Time,
"value": stat.Value,
"count": stat.SumCount,
"total": stat.SumTotal,
})
}
chartMaps = append(chartMaps, maps.Map{
"chart": maps.Map{
"id": chart.MetricChart.Id,
"name": chart.MetricChart.Name,
"widthDiv": chart.MetricChart.WidthDiv,
"isOn": chart.MetricChart.IsOn,
"maxItems": chart.MetricChart.MaxItems,
"type": chart.MetricChart.Type,
},
"item": maps.Map{
"id": chart.MetricChart.MetricItem.Id,
"name": chart.MetricChart.MetricItem.Name,
"period": chart.MetricChart.MetricItem.Period,
"periodUnit": chart.MetricChart.MetricItem.PeriodUnit,
"valueType": serverconfigs.FindMetricValueType(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"valueTypeName": serverconfigs.FindMetricValueName(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"keys": chart.MetricChart.MetricItem.Keys,
},
"stats": statMaps,
})
}
this.Data["metricCharts"] = chartMaps
}
this.Success()
}

View File

@@ -0,0 +1,121 @@
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"net"
"strconv"
"strings"
)
type CreateBatchAction struct {
actionutils.ParentAction
}
func (this *CreateBatchAction) Init() {
this.Nav("", "node", "create")
this.SecondMenu("nodes")
}
func (this *CreateBatchAction) RunGet(params struct {
ClusterId int64
}) {
leftMenuItems := []maps.Map{
{
"name": this.Lang(codes.NodeMenu_CreateSingleNode),
"url": "/clusters/cluster/createNode?clusterId=" + strconv.FormatInt(params.ClusterId, 10),
"isActive": false,
},
{
"name": this.Lang(codes.NodeMenu_CreateMultipleNodes),
"url": "/clusters/cluster/createBatch?clusterId=" + strconv.FormatInt(params.ClusterId, 10),
"isActive": true,
},
}
this.Data["leftMenuItems"] = leftMenuItems
// 限额
maxNodes, leftNodes, err := this.findNodesQuota()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["quota"] = maps.Map{
"maxNodes": maxNodes,
"leftNodes": leftNodes,
}
this.Show()
}
func (this *CreateBatchAction) RunPost(params struct {
ClusterId int64
GroupId int64
RegionId int64
IpList string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
if params.ClusterId <= 0 {
this.Fail("请选择正确的集群")
}
// 校验
// TODO 支持IP范围比如192.168.1.[100-105]
realIPList := []string{}
for _, ip := range strings.Split(params.IpList, "\n") {
ip = strings.TrimSpace(ip)
if len(ip) == 0 {
continue
}
ip = strings.ReplaceAll(ip, " ", "")
if net.ParseIP(ip) == nil {
this.Fail("发现错误的IP地址" + ip)
}
if lists.ContainsString(realIPList, ip) {
continue
}
realIPList = append(realIPList, ip)
}
// 保存
for _, ip := range realIPList {
resp, err := this.RPC().NodeRPC().CreateNode(this.AdminContext(), &pb.CreateNodeRequest{
Name: ip,
NodeClusterId: params.ClusterId,
NodeGroupId: params.GroupId,
NodeRegionId: params.RegionId,
NodeLogin: nil,
})
if err != nil {
this.ErrorPage(err)
return
}
nodeId := resp.NodeId
_, err = this.RPC().NodeIPAddressRPC().CreateNodeIPAddress(this.AdminContext(), &pb.CreateNodeIPAddressRequest{
NodeId: nodeId,
Role: nodeconfigs.NodeRoleNode,
Name: "IP地址",
Ip: ip,
CanAccess: true,
IsUp: true,
})
if err != nil {
this.ErrorPage(err)
return
}
}
// 创建日志
defer this.CreateLogInfo(codes.Node_LogCreateNodeBatch)
this.Success()
}

View File

@@ -0,0 +1,8 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package cluster
func (this *CreateBatchAction) findNodesQuota() (maxNodes int32, leftNodes int32, err error) {
return
}

View File

@@ -0,0 +1,20 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package cluster
import "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
func (this *CreateBatchAction) findNodesQuota() (maxNodes int32, leftNodes int32, err error) {
quotaResp, err := this.RPC().AuthorityKeyRPC().FindAuthorityQuota(this.AdminContext(), &pb.FindAuthorityQuotaRequest{})
if err != nil {
return 0, 0, err
}
leftNodes = quotaResp.MaxNodes - quotaResp.CountNodes
if leftNodes < 0 {
leftNodes = 0
}
return quotaResp.MaxNodes, leftNodes, nil
}

View File

@@ -0,0 +1,346 @@
package cluster
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"net"
"regexp"
"strconv"
"strings"
)
// CreateNodeAction 创建节点
type CreateNodeAction struct {
actionutils.ParentAction
}
func (this *CreateNodeAction) Init() {
this.Nav("", "node", "create")
this.SecondMenu("nodes")
}
func (this *CreateNodeAction) RunGet(params struct {
ClusterId int64
}) {
if params.ClusterId <= 0 {
this.RedirectURL("/clusters")
return
}
var leftMenuItems = []maps.Map{
{
"name": this.Lang(codes.NodeMenu_CreateSingleNode),
"url": "/clusters/cluster/createNode?clusterId=" + strconv.FormatInt(params.ClusterId, 10),
"isActive": true,
},
{
"name": this.Lang(codes.NodeMenu_CreateMultipleNodes),
"url": "/clusters/cluster/createBatch?clusterId=" + strconv.FormatInt(params.ClusterId, 10),
"isActive": false,
},
}
this.Data["leftMenuItems"] = leftMenuItems
// DNS线路
clusterDNSResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var dnsRouteMaps = []maps.Map{}
this.Data["dnsDomainId"] = 0
if clusterDNSResp.Domain != nil {
domainId := clusterDNSResp.Domain.Id
this.Data["dnsDomainId"] = domainId
if domainId > 0 {
routesResp, err := this.RPC().DNSDomainRPC().FindAllDNSDomainRoutes(this.AdminContext(), &pb.FindAllDNSDomainRoutesRequest{DnsDomainId: domainId})
if err != nil {
this.ErrorPage(err)
return
}
for _, route := range routesResp.Routes {
dnsRouteMaps = append(dnsRouteMaps, maps.Map{
"domainId": domainId,
"domainName": clusterDNSResp.Domain.Name,
"name": route.Name,
"code": route.Code,
})
}
}
}
this.Data["dnsRoutes"] = dnsRouteMaps
// API节点列表
apiNodesResp, err := this.RPC().APINodeRPC().FindAllEnabledAPINodes(this.AdminContext(), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
var apiNodes = apiNodesResp.ApiNodes
var apiEndpoints = []string{}
for _, apiNode := range apiNodes {
if !apiNode.IsOn {
continue
}
apiEndpoints = append(apiEndpoints, apiNode.AccessAddrs...)
}
this.Data["apiEndpoints"] = "\"" + strings.Join(apiEndpoints, "\", \"") + "\""
// 安装文件下载
this.Data["installerFiles"] = clusterutils.ListInstallerFiles()
// 限额
maxNodes, leftNodes, err := this.findNodesQuota()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["quota"] = maps.Map{
"maxNodes": maxNodes,
"leftNodes": leftNodes,
}
this.Show()
}
func (this *CreateNodeAction) RunPost(params struct {
Name string
IpAddressesJSON []byte
ClusterId int64
GroupId int64
RegionId int64
GrantId int64
SshHost string
SshPort int
DnsDomainId int64
DnsRoutesJSON []byte
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入节点名称")
if len(params.IpAddressesJSON) == 0 {
this.Fail("请至少添加一个IP地址")
}
// TODO 检查cluster
if params.ClusterId <= 0 {
this.Fail("请选择所在集群")
}
// IP地址
var ipAddresses = []maps.Map{}
if len(params.IpAddressesJSON) > 0 {
err := json.Unmarshal(params.IpAddressesJSON, &ipAddresses)
if err != nil {
this.ErrorPage(err)
return
}
}
if len(ipAddresses) == 0 {
// 检查Name中是否包含IP
var ipv4Reg = regexp.MustCompile(`\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}`)
var ipMatches = ipv4Reg.FindStringSubmatch(params.Name)
if len(ipMatches) > 0 {
var nodeIP = ipMatches[0]
if net.ParseIP(nodeIP) != nil {
ipAddresses = []maps.Map{
{
"ip": nodeIP,
"canAccess": true,
"isOn": true,
"isUp": true,
},
}
}
}
if len(ipAddresses) == 0 {
this.Fail("请至少输入一个IP地址")
}
}
var dnsRouteCodes = []string{}
if len(params.DnsRoutesJSON) > 0 {
err := json.Unmarshal(params.DnsRoutesJSON, &dnsRouteCodes)
if err != nil {
this.ErrorPage(err)
return
}
}
// TODO 检查登录授权
var loginInfo = &pb.NodeLogin{
Id: 0,
Name: "SSH",
Type: "ssh",
Params: maps.Map{
"grantId": params.GrantId,
"host": params.SshHost,
"port": params.SshPort,
}.AsJSON(),
}
// 保存
createResp, err := this.RPC().NodeRPC().CreateNode(this.AdminContext(), &pb.CreateNodeRequest{
Name: params.Name,
NodeClusterId: params.ClusterId,
NodeGroupId: params.GroupId,
NodeRegionId: params.RegionId,
NodeLogin: loginInfo,
DnsDomainId: params.DnsDomainId,
DnsRoutes: dnsRouteCodes,
})
if err != nil {
this.ErrorPage(err)
return
}
var nodeId = createResp.NodeId
// IP地址
var resultIPAddresses = []string{}
for _, addr := range ipAddresses {
var resultAddrIds = []int64{}
addrId := addr.GetInt64("id")
if addrId > 0 {
resultAddrIds = append(resultAddrIds, addrId)
_, err = this.RPC().NodeIPAddressRPC().UpdateNodeIPAddressNodeId(this.AdminContext(), &pb.UpdateNodeIPAddressNodeIdRequest{
NodeIPAddressId: addrId,
NodeId: nodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
resultIPAddresses = append(resultIPAddresses, addr.GetString("ip"))
} else {
var ipStrings = addr.GetString("ip")
result, err := utils.ExtractIP(ipStrings)
if err != nil {
this.Fail("节点创建成功但是保存IP失败" + err.Error())
}
resultIPAddresses = append(resultIPAddresses, result...)
if len(result) == 1 {
// 单个创建
createResp, err := this.RPC().NodeIPAddressRPC().CreateNodeIPAddress(this.AdminContext(), &pb.CreateNodeIPAddressRequest{
NodeId: nodeId,
Role: nodeconfigs.NodeRoleNode,
Name: addr.GetString("name"),
Ip: result[0],
CanAccess: addr.GetBool("canAccess"),
IsUp: addr.GetBool("isUp"),
})
if err != nil {
this.ErrorPage(err)
return
}
addrId = createResp.NodeIPAddressId
resultAddrIds = append(resultAddrIds, addrId)
} else if len(result) > 1 {
// 批量创建
createResp, err := this.RPC().NodeIPAddressRPC().CreateNodeIPAddresses(this.AdminContext(), &pb.CreateNodeIPAddressesRequest{
NodeId: nodeId,
Role: nodeconfigs.NodeRoleNode,
Name: addr.GetString("name"),
IpList: result,
CanAccess: addr.GetBool("canAccess"),
IsUp: addr.GetBool("isUp"),
GroupValue: ipStrings,
})
if err != nil {
this.ErrorPage(err)
return
}
resultAddrIds = append(resultAddrIds, createResp.NodeIPAddressIds...)
}
}
// 阈值
var thresholds = addr.GetSlice("thresholds")
if len(thresholds) > 0 {
thresholdsJSON, err := json.Marshal(thresholds)
if err != nil {
this.ErrorPage(err)
return
}
for _, addrId := range resultAddrIds {
_, err = this.RPC().NodeIPAddressThresholdRPC().UpdateAllNodeIPAddressThresholds(this.AdminContext(), &pb.UpdateAllNodeIPAddressThresholdsRequest{
NodeIPAddressId: addrId,
NodeIPAddressThresholdsJSON: thresholdsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
}
}
}
// 创建日志
defer this.CreateLogInfo(codes.Node_LogCreateNode, nodeId)
// 响应数据
this.Data["nodeId"] = nodeId
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: nodeId})
if err != nil {
this.ErrorPage(err)
return
}
if nodeResp.Node != nil {
var grantMap maps.Map = nil
grantId := params.GrantId
if grantId > 0 {
grantResp, err := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{NodeGrantId: grantId})
if err != nil {
this.ErrorPage(err)
return
}
if grantResp.NodeGrant != nil && grantResp.NodeGrant.Id > 0 {
grantMap = maps.Map{
"id": grantResp.NodeGrant.Id,
"name": grantResp.NodeGrant.Name,
"method": grantResp.NodeGrant.Method,
"methodName": grantutils.FindGrantMethodName(grantResp.NodeGrant.Method, this.LangCode()),
"username": grantResp.NodeGrant.Username,
}
}
}
this.Data["node"] = maps.Map{
"id": nodeResp.Node.Id,
"name": nodeResp.Node.Name,
"uniqueId": nodeResp.Node.UniqueId,
"secret": nodeResp.Node.Secret,
"addresses": resultIPAddresses,
"login": maps.Map{
"id": 0,
"name": "SSH",
"type": "ssh",
"params": maps.Map{
"grantId": params.GrantId,
"host": params.SshHost,
"port": params.SshPort,
},
},
"grant": grantMap,
}
}
this.Success()
}

View File

@@ -0,0 +1,77 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package cluster
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 CreateNodeInstallAction struct {
actionutils.ParentAction
}
func (this *CreateNodeInstallAction) RunPost(params struct {
NodeId int64
SshHost string
SshPort int
GrantId int64
Must *actions.Must
}) {
defer this.CreateLogInfo(codes.NodeSSH_LogUpdateNodeSSH, params.NodeId)
params.Must.
Field("sshHost2", params.SshHost).
Require("请填写SSH主机地址").
Field("sshPort2", params.SshPort).
Gt(0, "请填写SSH主机端口").
Lt(65535, "SSH主机端口需要小于65535").
Field("grantId", params.GrantId).
Gt(0, "请选择SSH登录认证")
// 查询login
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var node = nodeResp.Node
if node == nil {
this.Fail("找不到要修改的节点")
}
var loginId int64
if node.NodeLogin != nil {
loginId = node.NodeLogin.Id
}
// 修改节点信息
_, err = this.RPC().NodeRPC().UpdateNodeLogin(this.AdminContext(), &pb.UpdateNodeLoginRequest{
NodeId: params.NodeId,
NodeLogin: &pb.NodeLogin{
Id: loginId,
Name: "SSH",
Type: "ssh",
Params: maps.Map{
"grantId": params.GrantId,
"host": params.SshHost,
"port": params.SshPort,
}.AsJSON(),
},
})
if err != nil {
this.ErrorPage(err)
return
}
// 开始安装
_, err = this.RPC().NodeRPC().InstallNode(this.AdminContext(), &pb.InstallNodeRequest{NodeId: params.NodeId})
if err != nil {
this.Fail("安装失败:" + err.Error())
}
this.Success()
}

View File

@@ -0,0 +1,8 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package cluster
func (this *CreateNodeAction) findNodesQuota() (maxNodes int32, leftNodes int32, err error) {
return
}

View File

@@ -0,0 +1,20 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package cluster
import "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
func (this *CreateNodeAction) findNodesQuota() (maxNodes int32, leftNodes int32, err error) {
quotaResp, err := this.RPC().AuthorityKeyRPC().FindAuthorityQuota(this.AdminContext(), &pb.FindAuthorityQuotaRequest{})
if err != nil {
return 0, 0, err
}
leftNodes = quotaResp.MaxNodes - quotaResp.CountNodes
if leftNodes < 0 {
leftNodes = 0
}
return quotaResp.MaxNodes, leftNodes, nil
}

View File

@@ -0,0 +1,46 @@
package cluster
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("", "delete", "index")
this.SecondMenu("nodes")
}
func (this *DeleteAction) RunGet(params struct{}) {
this.Show()
}
func (this *DeleteAction) RunPost(params struct {
ClusterId int64
}) {
// 检查有无服务正在使用
countResp, err := this.RPC().ServerRPC().CountAllEnabledServersWithNodeClusterId(this.AdminContext(), &pb.CountAllEnabledServersWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
if countResp.Count > 0 {
this.Fail("有代理服务正在使用此集群,请修改这些代理服务后再删除")
}
// 删除
_, err = this.RPC().NodeClusterRPC().DeleteNodeCluster(this.AdminContext(), &pb.DeleteNodeClusterRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.NodeCluster_LogDeleteCluster, params.ClusterId)
this.Success()
}

View File

@@ -0,0 +1,70 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/types"
"io"
"net/http"
"os"
"regexp"
)
type DownloadInstallerAction struct {
actionutils.ParentAction
}
func (this *DownloadInstallerAction) Init() {
this.Nav("", "", "")
}
func (this *DownloadInstallerAction) RunGet(params struct {
Name string
}) {
if len(params.Name) == 0 {
this.ResponseWriter.WriteHeader(http.StatusNotFound)
this.WriteString("file not found")
return
}
// 检查文件名
// 以防止路径穿越等风险
if !regexp.MustCompile(`^[a-zA-Z0-9.-]+$`).MatchString(params.Name) {
this.ResponseWriter.WriteHeader(http.StatusNotFound)
this.WriteString("file not found")
return
}
var zipFile = Tea.Root + "/edge-api/deploy/" + params.Name
fp, err := os.OpenFile(zipFile, os.O_RDWR, 0444)
if err != nil {
if os.IsNotExist(err) {
this.ResponseWriter.WriteHeader(http.StatusNotFound)
this.WriteString("file not found")
return
}
this.ResponseWriter.WriteHeader(http.StatusInternalServerError)
this.WriteString("file can not be opened")
return
}
defer func() {
_ = fp.Close()
}()
stat, err := fp.Stat()
if err != nil {
this.ResponseWriter.WriteHeader(http.StatusInternalServerError)
this.WriteString("file can not be opened")
return
}
this.AddHeader("Content-Disposition", "attachment; filename=\""+params.Name+"\";")
this.AddHeader("Content-Type", "application/zip")
this.AddHeader("Content-Length", types.String(stat.Size()))
_, _ = io.Copy(this.ResponseWriter, fp)
}

View File

@@ -0,0 +1,54 @@
package groups
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 CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
ClusterId int64
Name string
Must *actions.Must
}) {
if params.ClusterId <= 0 {
this.Fail("请选择集群")
}
params.Must.
Field("name", params.Name).
Require("请输入分组名称")
createResp, err := this.RPC().NodeGroupRPC().CreateNodeGroup(this.AdminContext(), &pb.CreateNodeGroupRequest{
NodeClusterId: params.ClusterId,
Name: params.Name,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["group"] = maps.Map{
"id": createResp.NodeGroupId,
"name": params.Name,
}
// 创建日志
defer this.CreateLogInfo(codes.NodeGroup_LogCreateNodeGroup, createResp.NodeGroupId)
this.Success()
}

View File

@@ -0,0 +1,37 @@
package groups
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 {
GroupId int64
}) {
// 检查是否正在使用
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesWithNodeGroupId(this.AdminContext(), &pb.CountAllEnabledNodesWithNodeGroupIdRequest{NodeGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
if countResp.Count > 0 {
this.Fail("此分组正在被使用不能删除,请修改节点后再删除")
}
_, err = this.RPC().NodeGroupRPC().DeleteNodeGroup(this.AdminContext(), &pb.DeleteNodeGroupRequest{NodeGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.NodeGroup_LogDeleteNodeGroup, params.GroupId)
this.Success()
}

View File

@@ -0,0 +1,47 @@
package groups
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("", "node", "group")
this.SecondMenu("nodes")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
groupsResp, err := this.RPC().NodeGroupRPC().FindAllEnabledNodeGroupsWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodeGroupsWithNodeClusterIdRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
groupMaps := []maps.Map{}
for _, group := range groupsResp.NodeGroups {
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesWithNodeGroupId(this.AdminContext(), &pb.CountAllEnabledNodesWithNodeGroupIdRequest{NodeGroupId: group.Id})
if err != nil {
this.ErrorPage(err)
return
}
countNodes := countResp.Count
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": group.Name,
"countNodes": countNodes,
})
}
this.Data["groups"] = groupMaps
this.Show()
}

View File

@@ -0,0 +1,64 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type SelectPopupAction struct {
actionutils.ParentAction
}
func (this *SelectPopupAction) Init() {
this.Nav("", "", "")
}
func (this *SelectPopupAction) RunGet(params struct {
ClusterId int64
}) {
groupsResp, err := this.RPC().NodeGroupRPC().FindAllEnabledNodeGroupsWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodeGroupsWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
}
groupMaps := []maps.Map{}
for _, group := range groupsResp.NodeGroups {
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": group.Name,
})
}
this.Data["groups"] = groupMaps
this.Show()
}
func (this *SelectPopupAction) RunPost(params struct {
GroupId int64
Must *actions.Must
}) {
if params.GroupId <= 0 {
this.Fail("请选择要使用的分组")
}
groupResp, err := this.RPC().NodeGroupRPC().FindEnabledNodeGroup(this.AdminContext(), &pb.FindEnabledNodeGroupRequest{NodeGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
group := groupResp.NodeGroup
if group == nil {
this.NotFound("nodeGroup", params.GroupId)
return
}
this.Data["group"] = maps.Map{
"id": group.Id,
"name": group.Name,
}
this.Success()
}

View File

@@ -0,0 +1,26 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type SortAction struct {
actionutils.ParentAction
}
func (this *SortAction) RunPost(params struct {
GroupIds []int64
}) {
_, err := this.RPC().NodeGroupRPC().UpdateNodeGroupOrders(this.AdminContext(), &pb.UpdateNodeGroupOrdersRequest{NodeGroupIds: params.GroupIds})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.NodeGroup_LogSortNodeGroups)
this.Success()
}

View File

@@ -0,0 +1,63 @@
package groups
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 UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
GroupId int64
}) {
groupResp, err := this.RPC().NodeGroupRPC().FindEnabledNodeGroup(this.AdminContext(), &pb.FindEnabledNodeGroupRequest{NodeGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
group := groupResp.NodeGroup
if group == nil {
this.NotFound("nodeGroup", params.GroupId)
return
}
this.Data["group"] = maps.Map{
"id": group.Id,
"name": group.Name,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
GroupId int64
Name string
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入分组名称")
_, err := this.RPC().NodeGroupRPC().UpdateNodeGroup(this.AdminContext(), &pb.UpdateNodeGroupRequest{
NodeGroupId: params.GroupId,
Name: params.Name,
})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.NodeGroup_LogUpdateNodeGroup, params.GroupId)
this.Success()
}

View File

@@ -0,0 +1,27 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package cluster
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"strconv"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
if teaconst.IsPlus {
this.RedirectURL("/clusters/cluster/boards?clusterId=" + strconv.FormatInt(params.ClusterId, 10))
} else {
this.RedirectURL("/clusters/cluster/nodes?clusterId=" + strconv.FormatInt(params.ClusterId, 10))
}
}

View File

@@ -0,0 +1,75 @@
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/groups"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/cache"
ddosProtection "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/ddos-protection"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/dns"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/ssh"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/system"
clusters "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNode)).
Helper(clusters.NewClusterHelper()).
Data("teaMenu", "clusters").
Data("teaSubMenu", "cluster").
Prefix("/clusters/cluster").
Get("", new(IndexAction)).
Get("/nodes", new(NodesAction)).
GetPost("/installNodes", new(InstallNodesAction)).
GetPost("/installRemote", new(InstallRemoteAction)).
Post("/installStatus", new(InstallStatusAction)).
GetPost("/upgradeRemote", new(UpgradeRemoteAction)).
Post("/upgradeStatus", new(UpgradeStatusAction)).
GetPost("/delete", new(DeleteAction)).
GetPost("/createNode", new(CreateNodeAction)).
Post("/createNodeInstall", new(CreateNodeInstallAction)).
GetPost("/createBatch", new(CreateBatchAction)).
GetPost("/updateNodeSSH", new(UpdateNodeSSHAction)).
GetPost("/installManual", new(InstallManualAction)).
Post("/suggestLoginPorts", new(SuggestLoginPortsAction)).
Get("/downloadInstaller", new(DownloadInstallerAction)).
// 节点相关
Prefix("/clusters/cluster/node").
Get("", new(node.IndexAction)).
GetPost("/update", new(node.UpdateAction)).
GetPost("/install", new(node.InstallAction)).
Post("/updateInstallStatus", new(node.UpdateInstallStatusAction)).
Post("/status", new(node.StatusAction)).
Get("/logs", new(node.LogsAction)).
Post("/start", new(node.StartAction)).
Post("/stop", new(node.StopAction)).
Post("/uninstall", new(node.UninstallAction)).
Post("/up", new(node.UpAction)).
Post("/updateIsOn", new(node.UpdateIsOnAction)).
Get("/detail", new(node.DetailAction)).
GetPost("/updateDNSPopup", new(node.UpdateDNSPopupAction)).
Post("/syncDomain", new(node.SyncDomainAction)).
GetPost("/settings/cache", new(cache.IndexAction)).
GetPost("/settings/dns", new(dns.IndexAction)).
GetPost("/settings/system", new(system.IndexAction)).
GetPost("/settings/ssh", new(ssh.IndexAction)).
GetPost("/settings/ssh/test", new(ssh.TestAction)).
GetPost("/settings/ddos-protection", new(ddosProtection.IndexAction)).
Post("/settings/ddos-protection/status", new(ddosProtection.StatusAction)).
// 分组相关
Prefix("/clusters/cluster/groups").
Get("", new(groups.IndexAction)).
GetPost("/createPopup", new(groups.CreatePopupAction)).
GetPost("/updatePopup", new(groups.UpdatePopupAction)).
Post("/delete", new(groups.DeleteAction)).
Post("/sort", new(groups.SortAction)).
GetPost("/selectPopup", new(groups.SelectPopupAction)).
EndAll()
})
}

View File

@@ -0,0 +1,73 @@
//go:build plus
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/boards"
nodeboards "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/boards"
nodeschedule "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/schedule"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/schedule/actions"
nodethresholds "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/thresholds"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/cc"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/http3"
networksecurity "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/network-security"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/pages"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/thresholds"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/uam"
clusters "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeNode)).
Helper(clusters.NewClusterHelper()).
Data("teaMenu", "clusters").
Data("teaSubMenu", "cluster").
// 节点相关
Prefix("/clusters/cluster/node").
GetPost("/boards", new(nodeboards.IndexAction)).
Post("/boards/data", new(nodeboards.DataAction)).
Post("/boards/domainStats", new(nodeboards.DomainStatsAction)).
// 看板相关
Prefix("/clusters/cluster/boards").
GetPost("", new(boards.IndexAction)).
Post("/domainStats", new(boards.DomainStatsAction)).
// 集群设置相关
Prefix("/clusters/cluster/settings").
GetPost("/uam", new(uam.IndexAction)).
GetPost("/cc", new(cc.IndexAction)).
GetPost("/pages", new(pages.IndexAction)).
//
GetPost("/http3", new(http3.IndexAction)).
GetPost("/http3/test", new(http3.TestAction)).
//
GetPost("/thresholds", new(thresholds.IndexAction)).
//
GetPost("/network-security", new(networksecurity.IndexAction)).
// 节点设置相关
Prefix("/clusters/cluster/node/settings").
Get("/thresholds", new(nodethresholds.IndexAction)).
GetPost("/schedule", new(nodeschedule.IndexAction)).
Post("/schedule/resetActionStatus", new(nodeschedule.ResetActionStatusAction)).
GetPost("/schedule/actions/createPopup", new(scheduleactions.CreatePopupAction)).
GetPost("/schedule/actions/updatePopup", new(scheduleactions.UpdatePopupAction)).
Post("/schedule/actions/delete", new(scheduleactions.DeleteAction)).
Post("/schedule/actions/copyToGroup", new(scheduleactions.CopyToGroupAction)).
Post("/schedule/actions/copyToCluster", new(scheduleactions.CopyToClusterAction)).
Post("/schedule/actions/updateOrders", new(scheduleactions.UpdateActionsAction)).
//
EndAll()
})
}

View File

@@ -0,0 +1,67 @@
package cluster
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type InstallManualAction struct {
actionutils.ParentAction
}
func (this *InstallManualAction) Init() {
this.Nav("", "node", "install")
this.SecondMenu("nodes")
}
func (this *InstallManualAction) RunGet(params struct {
ClusterId int64
}) {
this.Data["leftMenuItems"] = LeftMenuItemsForInstall(this.AdminContext(), params.ClusterId, "manual", this.LangCode())
nodesResp, err := this.RPC().NodeRPC().FindAllNotInstalledNodesWithNodeClusterId(this.AdminContext(), &pb.FindAllNotInstalledNodesWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
nodeMaps := []maps.Map{}
for _, node := range nodesResp.Nodes {
loginParams := maps.Map{}
if node.NodeLogin != nil && len(node.NodeLogin.Params) > 0 {
err := json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
}
installStatus := maps.Map{
"isRunning": false,
"isFinished": false,
}
if node.InstallStatus != nil {
installStatus = maps.Map{
"isRunning": node.InstallStatus.IsRunning,
"isFinished": node.InstallStatus.IsFinished,
"isOk": node.InstallStatus.IsOk,
"error": node.InstallStatus.Error,
}
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"isOn": node.IsOn,
"name": node.Name,
"addresses": node.IpAddresses,
"login": node.NodeLogin,
"loginParams": loginParams,
"installStatus": installStatus,
})
}
this.Data["nodes"] = nodeMaps
this.Show()
}

View File

@@ -0,0 +1,74 @@
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"strings"
)
type InstallNodesAction struct {
actionutils.ParentAction
}
func (this *InstallNodesAction) Init() {
this.Nav("", "node", "install")
this.SecondMenu("nodes")
}
func (this *InstallNodesAction) RunGet(params struct {
ClusterId int64
}) {
this.Data["leftMenuItems"] = LeftMenuItemsForInstall(this.AdminContext(), params.ClusterId, "register", this.LangCode())
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
if clusterResp.NodeCluster == nil {
this.NotFound("nodeCluster", params.ClusterId)
return
}
cluster := clusterResp.NodeCluster
clusterAPINodesResp, err := this.RPC().NodeClusterRPC().FindAPINodesWithNodeCluster(this.AdminContext(), &pb.FindAPINodesWithNodeClusterRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
if err != nil {
this.ErrorPage(err)
return
}
apiNodeAddrs := []string{}
if clusterAPINodesResp.UseAllAPINodes {
apiNodesResp, err := this.RPC().APINodeRPC().FindAllEnabledAPINodes(this.AdminContext(), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
for _, apiNode := range apiNodesResp.ApiNodes {
if !apiNode.IsOn {
continue
}
apiNodeAddrs = append(apiNodeAddrs, apiNode.AccessAddrs...)
}
} else {
for _, apiNode := range clusterAPINodesResp.ApiNodes {
if !apiNode.IsOn {
continue
}
apiNodeAddrs = append(apiNodeAddrs, apiNode.AccessAddrs...)
}
}
this.Data["cluster"] = maps.Map{
"uniqueId": cluster.UniqueId,
"secret": cluster.Secret,
"endpoints": "\"" + strings.Join(apiNodeAddrs, "\", \"") + "\"",
}
this.Show()
}

View File

@@ -0,0 +1,86 @@
package cluster
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/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type InstallRemoteAction struct {
actionutils.ParentAction
}
func (this *InstallRemoteAction) Init() {
this.Nav("", "node", "install")
this.SecondMenu("nodes")
}
func (this *InstallRemoteAction) RunGet(params struct {
ClusterId int64
}) {
this.Data["leftMenuItems"] = LeftMenuItemsForInstall(this.AdminContext(), params.ClusterId, "install", this.LangCode())
nodesResp, err := this.RPC().NodeRPC().FindAllNotInstalledNodesWithNodeClusterId(this.AdminContext(), &pb.FindAllNotInstalledNodesWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
nodeMaps := []maps.Map{}
for _, node := range nodesResp.Nodes {
loginParams := maps.Map{}
if node.NodeLogin != nil && len(node.NodeLogin.Params) > 0 {
err := json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
}
installStatus := maps.Map{
"isRunning": false,
"isFinished": false,
}
if node.InstallStatus != nil {
installStatus = maps.Map{
"isRunning": node.InstallStatus.IsRunning,
"isFinished": node.InstallStatus.IsFinished,
"isOk": node.InstallStatus.IsOk,
"error": node.InstallStatus.Error,
}
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"isOn": node.IsOn,
"name": node.Name,
"addresses": node.IpAddresses,
"login": node.NodeLogin,
"loginParams": loginParams,
"installStatus": installStatus,
})
}
this.Data["nodes"] = nodeMaps
this.Show()
}
func (this *InstallRemoteAction) RunPost(params struct {
NodeId int64
Must *actions.Must
}) {
_, err := this.RPC().NodeRPC().InstallNode(this.AdminContext(), &pb.InstallNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLogInfo(codes.Node_LogInstallNodeRemotely, params.NodeId)
this.Success()
}

View File

@@ -0,0 +1,35 @@
package cluster
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type InstallStatusAction struct {
actionutils.ParentAction
}
func (this *InstallStatusAction) RunPost(params struct {
NodeId int64
}) {
resp, err := this.RPC().NodeRPC().FindNodeInstallStatus(this.AdminContext(), &pb.FindNodeInstallStatusRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
if resp.InstallStatus == nil {
this.Data["status"] = nil
this.Success()
}
this.Data["status"] = maps.Map{
"isRunning": resp.InstallStatus.IsRunning,
"isFinished": resp.InstallStatus.IsFinished,
"isOk": resp.InstallStatus.IsOk,
"error": resp.InstallStatus.Error,
"errorCode": resp.InstallStatus.ErrorCode,
}
this.Success()
}

View File

@@ -0,0 +1,45 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package boards
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type DataAction struct {
actionutils.ParentAction
}
func (this *DataAction) RunPost(params struct {
ClusterId int64
NodeId int64
}) {
resp, err := this.RPC().ServerStatBoardRPC().ComposeServerStatNodeBoard(this.AdminContext(), &pb.ComposeServerStatNodeBoardRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["board"] = maps.Map{
"isActive": resp.IsActive,
"trafficInBytes": resp.TrafficInBytes,
"trafficOutBytes": resp.TrafficOutBytes,
"countConnections": resp.CountConnections,
"countRequests": resp.CountRequests,
"countAttackRequests": resp.CountAttackRequests,
"cpuUsage": resp.CpuUsage,
"memoryUsage": resp.MemoryUsage,
"memoryTotalSize": resp.MemoryTotalSize,
"load": resp.Load,
"cacheDiskSize": resp.CacheDiskSize,
"cacheMemorySize": resp.CacheMemorySize,
"monthlyTrafficBytes": resp.MonthlyTrafficBytes,
"todayTrafficBytes": resp.DailyTrafficBytes,
"yesterdayTrafficBytes": resp.LastDailyTrafficBytes,
}
this.Success()
}

View File

@@ -0,0 +1,52 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package boards
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 DomainStatsAction struct {
actionutils.ParentAction
}
func (this *DomainStatsAction) RunPost(params struct {
NodeId int64
}) {
var hourFrom = timeutil.Format("YmdH", time.Now().Add(-23*time.Hour))
var hourTo = timeutil.Format("YmdH")
resp, err := this.RPC().ServerDomainHourlyStatRPC().ListTopServerDomainStatsWithServerId(this.AdminContext(), &pb.ListTopServerDomainStatsWithServerIdRequest{
NodeId: params.NodeId,
HourFrom: hourFrom,
HourTo: hourTo,
Size: 10,
})
if err != nil {
this.ErrorPage(err)
return
}
// 域名排行
{
var statMaps = []maps.Map{}
for _, stat := range resp.DomainStats {
statMaps = append(statMaps, maps.Map{
"serverId": stat.ServerId,
"domain": stat.Domain,
"countRequests": stat.CountRequests,
"bytes": stat.Bytes,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
})
}
this.Data["topDomainStats"] = statMaps
}
this.Success()
}

View File

@@ -0,0 +1,304 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
package boards
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"strconv"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "board")
this.SecondMenu("nodes")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
NodeId int64
}) {
if !teaconst.IsPlus {
this.RedirectURL("/clusters/cluster/node?clusterId=" + strconv.FormatInt(params.ClusterId, 10) + "&nodeId=" + strconv.FormatInt(params.NodeId, 10))
return
}
pbNode, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
// disk information
this.Data["diskWritingSpeedMB"] = 0
this.Data["diskWritingSpeedMBDelta"] = 0
if !utils.JSONIsNull(pbNode.StatusJSON) {
var statusConfig = &nodeconfigs.NodeStatus{}
err = json.Unmarshal(pbNode.StatusJSON, statusConfig)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["diskWritingSpeedMB"] = statusConfig.DiskWritingSpeedMB
}
// max cache capacity
this.Data["cacheDiskCapacityBytes"] = 0
if pbNode.MaxCacheDiskCapacity == nil {
pbNode.MaxCacheDiskCapacity = &pb.SizeCapacity{Count: 0, Unit: "byte"}
}
var capacityBytes = (&shared.SizeCapacity{
Count: pbNode.MaxCacheDiskCapacity.Count,
Unit: pbNode.MaxCacheDiskCapacity.Unit,
}).Bytes()
if capacityBytes <= 0 && params.ClusterId > 0 {
// lookup cache policy for cluster
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
if clusterResp.NodeCluster != nil && clusterResp.NodeCluster.HttpCachePolicyId > 0 {
cachePolicyResp, err := this.RPC().HTTPCachePolicyRPC().FindEnabledHTTPCachePolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPCachePolicyConfigRequest{HttpCachePolicyId: clusterResp.NodeCluster.HttpCachePolicyId})
if err != nil {
this.ErrorPage(err)
return
}
var cachePolicyJSON = cachePolicyResp.HttpCachePolicyJSON
if !utils.JSONIsNull(cachePolicyJSON) {
var cachePolicy = &serverconfigs.HTTPCachePolicy{}
err = json.Unmarshal(cachePolicyJSON, cachePolicy)
if err != nil {
this.ErrorPage(err)
return
}
if cachePolicy.IsOn && cachePolicy.Type == serverconfigs.CachePolicyStorageFile && cachePolicy.Capacity != nil {
capacityBytes = cachePolicy.Capacity.Bytes()
}
}
}
}
if capacityBytes > 0 {
this.Data["cacheDiskCapacityBytes"] = capacityBytes
}
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ClusterId int64
NodeId int64
}) {
resp, err := this.RPC().ServerStatBoardRPC().ComposeServerStatNodeBoard(this.AdminContext(), &pb.ComposeServerStatNodeBoardRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["board"] = maps.Map{
"isActive": resp.IsActive,
"trafficInBytes": resp.TrafficInBytes,
"trafficOutBytes": resp.TrafficOutBytes,
"countConnections": resp.CountConnections,
"countRequests": resp.CountRequests,
"countAttackRequests": resp.CountAttackRequests,
"cpuUsage": resp.CpuUsage,
"memoryUsage": resp.MemoryUsage,
"memoryTotalSize": resp.MemoryTotalSize,
"load": resp.Load,
"cacheDiskSize": resp.CacheDiskSize,
"cacheMemorySize": resp.CacheMemorySize,
"monthlyTrafficBytes": resp.MonthlyTrafficBytes,
"todayTrafficBytes": resp.DailyTrafficBytes,
"yesterdayTrafficBytes": resp.LastDailyTrafficBytes,
}
// 24小时流量趋势
{
var statMaps = []maps.Map{}
for _, stat := range resp.HourlyTrafficStats {
statMaps = append(statMaps, maps.Map{
"bytes": stat.Bytes,
"cachedBytes": stat.CachedBytes,
"countRequests": stat.CountRequests,
"countCachedRequests": stat.CountCachedRequests,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
"day": stat.Hour[4:6] + "月" + stat.Hour[6:8] + "日",
"hour": stat.Hour[8:],
})
}
this.Data["hourlyStats"] = statMaps
}
// 15天流量趋势
{
var statMaps = []maps.Map{}
for _, stat := range resp.DailyTrafficStats {
statMaps = append(statMaps, maps.Map{
"bytes": stat.Bytes,
"cachedBytes": stat.CachedBytes,
"countRequests": stat.CountRequests,
"countCachedRequests": stat.CountCachedRequests,
"countAttackRequests": stat.CountAttackRequests,
"attackBytes": stat.AttackBytes,
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
})
}
this.Data["dailyStats"] = statMaps
}
// CPU
{
var statMaps = []maps.Map{}
for _, stat := range resp.CpuNodeValues {
var valueMap = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &valueMap)
if err != nil {
continue
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": valueMap.GetFloat32("usage"),
})
}
this.Data["cpuValues"] = statMaps
}
// Memory
{
var statMaps = []maps.Map{}
for _, stat := range resp.MemoryNodeValues {
var valueMap = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &valueMap)
if err != nil {
continue
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": valueMap.GetFloat32("usage"),
})
}
this.Data["memoryValues"] = statMaps
}
// Load
{
var statMaps = []maps.Map{}
for _, stat := range resp.LoadNodeValues {
var valueMap = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &valueMap)
if err != nil {
continue
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": valueMap.GetFloat32("load1m"),
})
}
this.Data["loadValues"] = statMaps
}
// CacheDirs
{
var statMaps = []maps.Map{}
for _, stat := range resp.CacheDirsValues {
var m = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &m)
if err != nil {
continue
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"value": m,
})
}
this.Data["cacheDirValues"] = statMaps
}
// Network Packets
{
var hasData = false
var statMaps = []maps.Map{}
for _, stat := range resp.NetworkPacketsValues {
var valueMap = maps.Map{}
err = json.Unmarshal(stat.ValueJSON, &valueMap)
if err != nil {
continue
}
var tcpInPPS = valueMap.GetUint64("tcpInPPS")
var udpInPPS = valueMap.GetUint64("udpInPPS")
var icmpInPPS = valueMap.GetUint64("icmpInPPS")
if tcpInPPS > 0 || udpInPPS > 0 || icmpInPPS > 0 {
hasData = true
}
statMaps = append(statMaps, maps.Map{
"time": timeutil.FormatTime("H:i", stat.CreatedAt),
"tcpInPPS": tcpInPPS,
"udpInPPS": udpInPPS,
"icmpInPPS": icmpInPPS,
})
}
if !hasData {
statMaps = []maps.Map{}
}
this.Data["networkPacketsValues"] = statMaps
}
// 指标
{
var chartMaps = []maps.Map{}
for _, chart := range resp.MetricDataCharts {
var statMaps = []maps.Map{}
for _, stat := range chart.MetricStats {
statMaps = append(statMaps, maps.Map{
"keys": stat.Keys,
"time": stat.Time,
"value": stat.Value,
"count": stat.SumCount,
"total": stat.SumTotal,
})
}
chartMaps = append(chartMaps, maps.Map{
"chart": maps.Map{
"id": chart.MetricChart.Id,
"name": chart.MetricChart.Name,
"widthDiv": chart.MetricChart.WidthDiv,
"isOn": chart.MetricChart.IsOn,
"maxItems": chart.MetricChart.MaxItems,
"type": chart.MetricChart.Type,
},
"item": maps.Map{
"id": chart.MetricChart.MetricItem.Id,
"name": chart.MetricChart.MetricItem.Name,
"period": chart.MetricChart.MetricItem.Period,
"periodUnit": chart.MetricChart.MetricItem.PeriodUnit,
"valueType": serverconfigs.FindMetricValueType(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"valueTypeName": serverconfigs.FindMetricValueName(chart.MetricChart.MetricItem.Category, chart.MetricChart.MetricItem.Value),
"keys": chart.MetricChart.MetricItem.Keys,
},
"stats": statMaps,
})
}
this.Data["metricCharts"] = chartMaps
}
this.Success()
}

View File

@@ -0,0 +1,403 @@
package node
import (
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/ipAddresses/ipaddressutils"
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type DetailAction struct {
actionutils.ParentAction
}
func (this *DetailAction) Init() {
this.Nav("", "node", "node")
this.SecondMenu("nodes")
}
func (this *DetailAction) RunGet(params struct {
NodeId int64
ClusterId int64
}) {
this.Data["nodeId"] = params.NodeId
nodeResp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var node = nodeResp.Node
if node == nil {
this.WriteString("找不到要操作的节点")
return
}
// 主集群
var clusterMap maps.Map = nil
if node.NodeCluster != nil {
var clusterId = node.NodeCluster.Id
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: clusterId})
if err != nil {
this.ErrorPage(err)
return
}
var cluster = clusterResp.NodeCluster
if cluster != nil {
clusterMap = maps.Map{
"id": cluster.Id,
"name": cluster.Name,
"installDir": cluster.InstallDir,
}
}
}
// 从集群
var secondaryClustersMaps = []maps.Map{}
for _, cluster := range node.SecondaryNodeClusters {
secondaryClustersMaps = append(secondaryClustersMaps, maps.Map{
"id": cluster.Id,
"name": cluster.Name,
"isOn": cluster.IsOn,
})
}
// 当前访问集群的DNS设置
clusterDNSInfo, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["dnsIsExcludingLnNode"] = clusterDNSInfo != nil && !clusterDNSInfo.IncludingLnNodes && node.Level > 1
// IP地址
ipAddressesResp, err := this.RPC().NodeIPAddressRPC().FindAllEnabledNodeIPAddressesWithNodeId(this.AdminContext(), &pb.FindAllEnabledNodeIPAddressesWithNodeIdRequest{
NodeId: params.NodeId,
Role: nodeconfigs.NodeRoleNode,
})
if err != nil {
this.ErrorPage(err)
return
}
var ipAddresses = ipAddressesResp.NodeIPAddresses
var ipAddressMaps = []maps.Map{}
for _, addr := range ipAddressesResp.NodeIPAddresses {
thresholds, err := ipaddressutils.InitNodeIPAddressThresholds(this.Parent(), addr.Id)
if err != nil {
this.ErrorPage(err)
return
}
// 是否有备用IP
var originIP = addr.Ip
if len(addr.BackupIP) > 0 {
addr.Ip = addr.BackupIP
}
// 专属集群
var addrClusterMaps = []maps.Map{}
for _, addrCluster := range addr.NodeClusters {
addrClusterMaps = append(addrClusterMaps, maps.Map{
"id": addrCluster.Id,
"name": addrCluster.Name,
})
}
ipAddressMaps = append(ipAddressMaps, maps.Map{
"id": addr.Id,
"name": addr.Name,
"originIP": originIP,
"ip": addr.Ip,
"canAccess": addr.CanAccess,
"isOn": addr.IsOn,
"isUp": addr.IsUp,
"clusters": addrClusterMaps,
"thresholds": thresholds,
})
}
// DNS相关
var clusters = []*pb.NodeCluster{node.NodeCluster}
clusters = append(clusters, node.SecondaryNodeClusters...)
var recordMaps = []maps.Map{}
var routeMaps = []maps.Map{}
for _, cluster := range clusters {
dnsInfoResp, err := this.RPC().NodeRPC().FindEnabledNodeDNS(this.AdminContext(), &pb.FindEnabledNodeDNSRequest{
NodeId: params.NodeId,
NodeClusterId: cluster.Id,
})
if err != nil {
this.ErrorPage(err)
return
}
var dnsInfo = dnsInfoResp.Node
if len(dnsInfo.DnsDomainName) == 0 || len(dnsInfo.NodeClusterDNSName) == 0 {
continue
}
var domainName = dnsInfo.DnsDomainName
// 默认线路
if len(dnsInfo.Routes) == 0 {
dnsInfo.Routes = append(dnsInfo.Routes, &pb.DNSRoute{})
} else {
for _, route := range dnsInfo.Routes {
routeMaps = append(routeMaps, maps.Map{
"domainName": domainName,
"code": route.Code,
"name": route.Name,
})
}
}
for _, addr := range ipAddresses {
if !addr.CanAccess || !addr.IsUp || !addr.IsOn {
continue
}
// 过滤集群
if len(addr.NodeClusters) > 0 {
var inCluster = false
for _, addrCluster := range addr.NodeClusters {
if addrCluster.Id == cluster.Id {
inCluster = true
}
}
if !inCluster {
continue
}
}
for _, route := range dnsInfo.Routes {
var recordType = "A"
if iputils.IsIPv6(addr.Ip) {
recordType = "AAAA"
}
recordMaps = append(recordMaps, maps.Map{
"name": dnsInfo.NodeClusterDNSName + "." + domainName,
"type": recordType,
"route": route.Name,
"value": addr.Ip,
"clusterName": cluster.Name,
"isBackup": dnsInfo.IsBackupForCluster || dnsInfo.IsBackupForGroup,
"isOffline": dnsInfo.IsOffline,
})
}
}
}
// 登录信息
var loginMap maps.Map = nil
if node.NodeLogin != nil {
loginParams := maps.Map{}
if len(node.NodeLogin.Params) > 0 {
err = json.Unmarshal(node.NodeLogin.Params, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
}
var grantMap = maps.Map{}
var grantId = loginParams.GetInt64("grantId")
if grantId > 0 {
grantResp, err := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{NodeGrantId: grantId})
if err != nil {
this.ErrorPage(err)
return
}
if grantResp.NodeGrant != nil {
grantMap = maps.Map{
"id": grantResp.NodeGrant.Id,
"name": grantResp.NodeGrant.Name,
"method": grantResp.NodeGrant.Method,
"methodName": grantutils.FindGrantMethodName(grantResp.NodeGrant.Method, this.LangCode()),
"username": grantResp.NodeGrant.Username,
}
}
}
loginMap = maps.Map{
"id": node.NodeLogin.Id,
"name": node.NodeLogin.Name,
"type": node.NodeLogin.Type,
"params": loginParams,
"grant": grantMap,
}
}
// 运行状态
var status = &nodeconfigs.NodeStatus{}
this.Data["nodeDatetime"] = ""
this.Data["nodeTimeDiff"] = 0
if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status)
if err != nil {
this.ErrorPage(err)
return
}
status.IsActive = status.IsActive && time.Now().Unix()-status.UpdatedAt <= 60 // N秒之内认为活跃
if status.Timestamp > 0 {
this.Data["nodeDatetime"] = timeutil.FormatTime("Y-m-d H:i:s", status.Timestamp)
if status.UpdatedAt > 0 {
var diff = status.UpdatedAt - status.Timestamp
if diff < 0 {
diff = -diff
}
this.Data["nodeTimeDiff"] = diff
}
} else if status.UpdatedAt > 0 {
this.Data["nodeDatetime"] = timeutil.FormatTime("Y-m-d H:i:s", status.UpdatedAt)
}
}
// 检查是否有新版本
if len(status.OS) > 0 {
checkVersionResp, err := this.RPC().NodeRPC().CheckNodeLatestVersion(this.AdminContext(), &pb.CheckNodeLatestVersionRequest{
Os: status.OS,
Arch: status.Arch,
CurrentVersion: status.BuildVersion,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["shouldUpgrade"] = checkVersionResp.HasNewVersion
this.Data["newVersion"] = checkVersionResp.NewVersion
} else {
this.Data["shouldUpgrade"] = false
this.Data["newVersion"] = ""
}
// 分组
var groupMap maps.Map = nil
if node.NodeGroup != nil {
groupMap = maps.Map{
"id": node.NodeGroup.Id,
"name": node.NodeGroup.Name,
}
}
// 区域
var regionMap maps.Map = nil
if node.NodeRegion != nil {
regionMap = maps.Map{
"id": node.NodeRegion.Id,
"name": node.NodeRegion.Name,
}
}
// 缓存硬盘 & 内存容量
var maxCacheDiskCapacity maps.Map
if node.MaxCacheDiskCapacity != nil {
maxCacheDiskCapacity = maps.Map{
"count": node.MaxCacheDiskCapacity.Count,
"unit": node.MaxCacheDiskCapacity.Unit,
}
} else {
maxCacheDiskCapacity = maps.Map{
"count": 0,
"unit": "gb",
}
}
var maxCacheMemoryCapacity maps.Map
if node.MaxCacheMemoryCapacity != nil {
maxCacheMemoryCapacity = maps.Map{
"count": node.MaxCacheMemoryCapacity.Count,
"unit": node.MaxCacheMemoryCapacity.Unit,
}
} else {
maxCacheMemoryCapacity = maps.Map{
"count": 0,
"unit": "gb",
}
}
var lnAddrs = node.LnAddrs
if lnAddrs == nil {
lnAddrs = []string{}
}
// API节点地址
var apiNodeAddrStrings = []string{}
var apiNodeAddrs = []*serverconfigs.NetworkAddressConfig{}
if len(node.ApiNodeAddrsJSON) > 0 {
err = json.Unmarshal(node.ApiNodeAddrsJSON, &apiNodeAddrs)
if err != nil {
this.ErrorPage(err)
return
}
for _, addr := range apiNodeAddrs {
if addr.Init() == nil {
apiNodeAddrStrings = append(apiNodeAddrStrings, addr.FullAddresses()...)
}
}
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"ipAddresses": ipAddressMaps,
"cluster": clusterMap,
"secondaryClusters": secondaryClustersMaps,
"login": loginMap,
"installDir": node.InstallDir,
"isInstalled": node.IsInstalled,
"uniqueId": node.UniqueId,
"secret": node.Secret,
"maxCPU": node.MaxCPU,
"isOn": node.IsOn,
"records": recordMaps,
"routes": routeMaps,
"level": node.Level,
"levelInfo": nodeconfigs.FindNodeLevel(int(node.Level)),
"lnAddrs": lnAddrs,
"enableIPLists": node.EnableIPLists,
"apiNodeAddrs": apiNodeAddrStrings,
"offlineDay": node.OfflineDay,
"isOffline": len(node.OfflineDay) > 0 && node.OfflineDay < timeutil.Format("Ymd"),
"isBackupForCluster": node.IsBackupForCluster,
"isBackupForGroup": node.IsBackupForGroup,
"status": maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
"memUsage": status.MemoryUsage,
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
"connectionCount": status.ConnectionCount,
"buildVersion": status.BuildVersion,
"cpuPhysicalCount": status.CPUPhysicalCount,
"cpuLogicalCount": status.CPULogicalCount,
"load1m": numberutils.FormatFloat2(status.Load1m),
"load5m": numberutils.FormatFloat2(status.Load5m),
"load15m": numberutils.FormatFloat2(status.Load15m),
"cacheTotalDiskSize": numberutils.FormatBytes(status.CacheTotalDiskSize),
"cacheTotalMemorySize": numberutils.FormatBytes(status.CacheTotalMemorySize),
"exePath": status.ExePath,
"apiSuccessPercent": status.APISuccessPercent,
"apiAvgCostSeconds": status.APIAvgCostSeconds,
"diskWritingSpeedMB": status.DiskWritingSpeedMB,
},
"group": groupMap,
"region": regionMap,
"maxCacheDiskCapacity": maxCacheDiskCapacity,
"maxCacheMemoryCapacity": maxCacheMemoryCapacity,
}
this.Show()
}

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