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,15 @@
package actionutils
import (
"context"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/iwind/TeaGo/maps"
)
type ActionInterface interface {
RPC() *rpc.RPCClient
AdminContext() context.Context
ViewData() maps.Map
}

View File

@@ -0,0 +1,22 @@
package actionutils
import (
"github.com/TeaOSLab/EdgeAdmin/internal/csrf"
"github.com/iwind/TeaGo/actions"
"net/http"
)
type CSRF struct {
}
func (this *CSRF) BeforeAction(actionPtr actions.ActionWrapper, paramName string) (goNext bool) {
action := actionPtr.Object()
token := action.ParamString("csrfToken")
if !csrf.Validate(token) {
action.ResponseWriter.WriteHeader(http.StatusForbidden)
action.WriteString("表单已失效,请刷新页面后重试(001)")
return
}
return true
}

View File

@@ -0,0 +1,44 @@
package actionutils
// Menu 子菜单定义
type Menu struct {
Id string `json:"id"`
Name string `json:"name"`
Items []*MenuItem `json:"items"`
IsActive bool `json:"isActive"`
AlwaysActive bool `json:"alwaysActive"`
Index int `json:"index"`
CountNormalItems int `json:"countNormalItems"`
}
// NewMenu 获取新对象
func NewMenu() *Menu {
return &Menu{
Items: []*MenuItem{},
}
}
// Add 添加菜单项
func (this *Menu) Add(name string, subName string, url string, isActive bool) *MenuItem {
item := &MenuItem{
Name: name,
SubName: subName,
URL: url,
IsActive: isActive,
}
this.CountNormalItems++
this.Items = append(this.Items, item)
if isActive {
this.IsActive = true
}
return item
}
// AddSpecial 添加特殊菜单项,不计数
func (this *Menu) AddSpecial(name string, subName string, url string, isActive bool) *MenuItem {
item := this.Add(name, subName, url, isActive)
this.CountNormalItems--
return item
}

View File

@@ -0,0 +1,48 @@
package actionutils
import (
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
)
// MenuGroup 菜单分组
type MenuGroup struct {
Menus []*Menu `json:"menus"`
AlwaysMenu *Menu `json:"alwaysMenu"`
}
// NewMenuGroup 获取新菜单分组对象
func NewMenuGroup() *MenuGroup {
return &MenuGroup{
Menus: []*Menu{},
}
}
// FindMenu 查找菜单,如果找不到则自动创建
func (this *MenuGroup) FindMenu(menuId string, menuName string) *Menu {
for _, m := range this.Menus {
if m.Id == menuId {
return m
}
}
menu := NewMenu()
menu.Id = menuId
menu.Name = menuName
menu.Items = []*MenuItem{}
this.Menus = append(this.Menus, menu)
return menu
}
// Sort 排序
func (this *MenuGroup) Sort() {
lists.Sort(this.Menus, func(i int, j int) bool {
menu1 := this.Menus[i]
menu2 := this.Menus[j]
return menu1.Index < menu2.Index
})
}
// SetSubMenu 设置子菜单
func SetSubMenu(action actions.ActionWrapper, menu *MenuGroup) {
action.Object().Data["teaSubMenus"] = menu
}

View File

@@ -0,0 +1,14 @@
package actionutils
// MenuItem 菜单项
type MenuItem struct {
Id string `json:"id"`
Name string `json:"name"`
SubName string `json:"subName"` // 副标题
SupName string `json:"supName"` // 头标
URL string `json:"url"`
IsActive bool `json:"isActive"`
Icon string `json:"icon"`
IsSortable bool `json:"isSortable"`
SubColor string `json:"subColor"`
}

View File

@@ -0,0 +1,161 @@
package actionutils
import (
"fmt"
"github.com/iwind/TeaGo/actions"
"math"
"net/url"
"strings"
)
type Page struct {
Offset int64 // 开始位置
Size int64 // 每页显示数量
Current int64 // 当前页码
Max int64 // 最大页码
Total int64 // 总数量
Path string
Query url.Values
}
func NewActionPage(actionPtr actions.ActionWrapper, total int64, size int64) *Page {
action := actionPtr.Object()
currentPage := action.ParamInt64("page")
paramSize := action.ParamInt64("pageSize")
if paramSize > 0 {
size = paramSize
}
if size <= 0 {
size = 10
}
page := &Page{
Current: currentPage,
Total: total,
Size: size,
Path: action.Request.URL.Path,
Query: action.Request.URL.Query(),
}
page.calculate()
return page
}
func (this *Page) calculate() {
if this.Current < 1 {
this.Current = 1
}
if this.Size <= 0 {
this.Size = 10
}
this.Offset = this.Size * (this.Current - 1)
this.Max = int64(math.Ceil(float64(this.Total) / float64(this.Size)))
}
func (this *Page) AsHTML() string {
if this.Total <= this.Size {
return ""
}
result := []string{}
// 首页
if this.Max > 0 {
result = append(result, `<a href="`+this.composeURL(1)+`">首页</a>`)
} else {
result = append(result, `<a>首页</a>`)
}
// 上一页
if this.Current <= 1 {
result = append(result, `<a>上一页</a>`)
} else {
result = append(result, `<a href="`+this.composeURL(this.Current-1)+`">上一页</a>`)
}
// 中间页数
before5 := this.max(this.Current-5, 1)
after5 := this.min(before5+9, this.Max)
if before5 > 1 {
result = append(result, `<a>...</a>`)
}
for i := before5; i <= after5; i++ {
if i == this.Current {
result = append(result, `<a href="`+this.composeURL(i)+`" class="active">`+fmt.Sprintf("%d", i)+`</a>`)
} else {
result = append(result, `<a href="`+this.composeURL(i)+`">`+fmt.Sprintf("%d", i)+`</a>`)
}
}
if after5 < this.Max {
result = append(result, `<a>...</a>`)
}
// 下一页
if this.Current >= this.Max {
result = append(result, "<a>下一页</a>")
} else {
result = append(result, `<a href="`+this.composeURL(this.Current+1)+`">下一页</a>`)
}
// 尾页
if this.Max > 0 {
result = append(result, `<a href="`+this.composeURL(this.Max)+`">尾页</a>`)
} else {
result = append(result, `<a>尾页</a>`)
}
// 每页数
result = append(result, `<select class="ui dropdown" style="padding-top:0;padding-bottom:0;margin-left:1em;color:#666" onchange="ChangePageSize(this.value)">
<option value="10">[每页]</option>`+this.renderSizeOption(10)+
this.renderSizeOption(20)+
this.renderSizeOption(30)+
this.renderSizeOption(40)+
this.renderSizeOption(50)+
this.renderSizeOption(60)+
this.renderSizeOption(70)+
this.renderSizeOption(80)+
this.renderSizeOption(90)+
this.renderSizeOption(100)+`
</select>`)
return `<div class="page">` + strings.Join(result, "") + `</div>`
}
// IsLastPage 判断是否为最后一页
func (this *Page) IsLastPage() bool {
return this.Current == this.Max
}
func (this *Page) composeURL(page int64) string {
this.Query["page"] = []string{fmt.Sprintf("%d", page)}
return this.Path + "?" + this.Query.Encode()
}
func (this *Page) min(i, j int64) int64 {
if i < j {
return i
}
return j
}
func (this *Page) max(i, j int64) int64 {
if i < j {
return j
}
return i
}
func (this *Page) renderSizeOption(size int64) string {
o := `<option value="` + fmt.Sprintf("%d", size) + `"`
if size == this.Size {
o += ` selected="selected"`
}
o += `>` + fmt.Sprintf("%d", size) + `条</option>`
return o
}

View File

@@ -0,0 +1,40 @@
package actionutils
import (
"net/url"
"testing"
)
func TestNewActionPage(t *testing.T) {
page := &Page{
Current: 3,
Total: 105,
Size: 20,
Path: "/hello",
Query: url.Values{
"a": []string{"b"},
"c": []string{"d"},
"page": []string{"3"},
},
}
page.calculate()
t.Log(page.AsHTML())
//logs.PrintAsJSON(page, t)
}
func TestNewActionPage2(t *testing.T) {
page := &Page{
Current: 3,
Total: 105,
Size: 10,
Path: "/hello",
Query: url.Values{
"a": []string{"b"},
"c": []string{"d"},
"page": []string{"3"},
},
}
page.calculate()
t.Log(page.AsHTML())
//logs.PrintAsJSON(page, t)
}

View File

@@ -0,0 +1,182 @@
package actionutils
import (
"context"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index/loginutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"net/http"
"strconv"
)
type ParentAction struct {
actions.ActionObject
rpcClient *rpc.RPCClient
ctx context.Context
}
// Parent 可以调用自身的一个简便方法
func (this *ParentAction) Parent() *ParentAction {
return this
}
func (this *ParentAction) ErrorPage(err error) {
if err == nil {
return
}
// 日志
this.CreateLog(oplogs.LevelError, codes.AdminCommon_LogSystemError, err.Error())
if this.Request.Method == http.MethodGet {
FailPage(this, err)
} else {
Fail(this, err)
}
}
func (this *ParentAction) ErrorText(err string) {
this.ErrorPage(errors.New(err))
}
func (this *ParentAction) NotFound(name string, itemId int64) {
if itemId > 0 {
this.ErrorPage(errors.New(name + " id: '" + strconv.FormatInt(itemId, 10) + "' is not found"))
} else {
this.ErrorPage(errors.New(name + " is not found"))
}
}
func (this *ParentAction) NewPage(total int64, size ...int64) *Page {
if len(size) > 0 {
return NewActionPage(this, total, size[0])
}
var pageSize int64 = 10
adminConfig, err := configloaders.LoadAdminUIConfig()
if err == nil && adminConfig.DefaultPageSize > 0 {
pageSize = int64(adminConfig.DefaultPageSize)
}
return NewActionPage(this, total, pageSize)
}
func (this *ParentAction) Nav(mainMenu string, tab string, firstMenu string) {
this.Data["mainMenu"] = mainMenu
this.Data["mainTab"] = tab
this.Data["firstMenuItem"] = firstMenu
}
func (this *ParentAction) FirstMenu(menuItem string) {
this.Data["firstMenuItem"] = menuItem
}
func (this *ParentAction) SecondMenu(menuItem string) {
this.Data["secondMenuItem"] = menuItem
}
func (this *ParentAction) TinyMenu(menuItem string) {
this.Data["tinyMenuItem"] = menuItem
}
func (this *ParentAction) AdminId() int64 {
return this.Context.GetInt64(teaconst.SessionAdminId)
}
func (this *ParentAction) CreateLog(level string, messageCode langs.MessageCode, args ...any) {
var description = messageCode.For(this.LangCode())
var desc = fmt.Sprintf(description, args...)
if level == oplogs.LevelInfo {
if this.Code != 200 {
level = oplogs.LevelWarn
if len(this.Message) > 0 {
desc += " 失败:" + this.Message
}
}
}
err := dao.SharedLogDAO.CreateAdminLog(this.AdminContext(), level, this.Request.URL.Path, desc, loginutils.RemoteIP(&this.ActionObject), messageCode, args)
if err != nil {
utils.PrintError(err)
}
}
func (this *ParentAction) CreateLogInfo(messageCode langs.MessageCode, args ...any) {
this.CreateLog(oplogs.LevelInfo, messageCode, args...)
}
// RPC 获取RPC
func (this *ParentAction) RPC() *rpc.RPCClient {
if this.rpcClient != nil {
return this.rpcClient
}
// 所有集群
rpcClient, err := rpc.SharedRPC()
if err != nil {
logs.Fatal(err)
return nil
}
this.rpcClient = rpcClient
return rpcClient
}
// AdminContext 获取Context
// 每个请求的context都必须是一个新的实例
func (this *ParentAction) AdminContext() context.Context {
if this.rpcClient == nil {
rpcClient, err := rpc.SharedRPC()
if err != nil {
logs.Fatal(err)
return nil
}
this.rpcClient = rpcClient
}
this.ctx = this.rpcClient.Context(this.AdminId())
return this.ctx
}
// ViewData 视图里可以使用的数据
func (this *ParentAction) ViewData() maps.Map {
return this.Data
}
func (this *ParentAction) LangCode() string {
return configloaders.FindAdminLangForAction(this)
}
func (this *ParentAction) Lang(messageCode langs.MessageCode, args ...any) string {
return langs.Message(this.LangCode(), messageCode, args...)
}
func (this *ParentAction) FailLang(messageCode langs.MessageCode, args ...any) {
this.Fail(langs.Message(this.LangCode(), messageCode, args...))
}
func (this *ParentAction) FailFieldLang(field string, messageCode langs.MessageCode, args ...any) {
this.FailField(field, langs.Message(this.LangCode(), messageCode, args...))
}
func (this *ParentAction) FilterHTTPFamily() bool {
if this.Data.GetString("serverFamily") == "http" {
return false
}
this.ResponseWriter.WriteHeader(http.StatusNotFound)
_, _ = this.ResponseWriter.Write([]byte("page not found"))
return true
}

View File

@@ -0,0 +1,54 @@
package actionutils
import (
"github.com/iwind/TeaGo/actions"
)
type TabItem struct {
Name string `json:"name"`
SubName string `json:"subName"`
URL string `json:"url"`
Icon string `json:"icon"`
IsActive bool `json:"isActive"`
IsRight bool `json:"isRight"`
IsTitle bool `json:"isTitle"`
IsDisabled bool `json:"isDisabled"`
}
// Tabbar Tabbar定义
type Tabbar struct {
items []*TabItem
}
// NewTabbar 获取新对象
func NewTabbar() *Tabbar {
return &Tabbar{
items: []*TabItem{},
}
}
// Add 添加菜单项
func (this *Tabbar) Add(name string, subName string, url string, icon string, active bool) *TabItem {
var m = &TabItem{
Name: name,
SubName: subName,
URL: url,
Icon: icon,
IsActive: active,
IsRight: false,
IsTitle: false,
IsDisabled: false,
}
this.items = append(this.items, m)
return m
}
// Items 取得所有的Items
func (this *Tabbar) Items() []*TabItem {
return this.items
}
// SetTabbar 设置子菜单
func SetTabbar(action actions.ActionWrapper, tabbar *Tabbar) {
action.Object().Data["teaTabbar"] = tabbar.Items()
}

View File

@@ -0,0 +1,208 @@
package actionutils
import (
"encoding/json"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
rpcerrors "github.com/TeaOSLab/EdgeCommon/pkg/rpc/errors"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/gosock/pkg/gosock"
"net"
"net/http"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"time"
)
// Fail 提示服务器错误信息
func Fail(actionPtr actions.ActionWrapper, err error) {
if err == nil {
err = errors.New("unknown error")
}
var langCode = configloaders.FindAdminLangForAction(actionPtr)
var serverErrString = codes.AdminCommon_ServerError.For(langCode)
logs.Println("[" + reflect.TypeOf(actionPtr).String() + "]" + findStack(err.Error()))
_, _, isLocalAPI, issuesHTML := parseAPIErr(actionPtr, err)
if isLocalAPI && len(issuesHTML) > 0 {
actionPtr.Object().Fail(serverErrString + "" + err.Error() + ";最近一次错误提示:" + issuesHTML + "")
} else {
actionPtr.Object().Fail(serverErrString + "" + err.Error() + "")
}
}
// FailPage 提示页面错误信息
func FailPage(actionPtr actions.ActionWrapper, err error) {
if err == nil {
err = errors.New("unknown error")
}
var langCode = configloaders.FindAdminLangForAction(actionPtr)
var serverErrString = codes.AdminCommon_ServerError.For(langCode)
logs.Println("[" + reflect.TypeOf(actionPtr).String() + "]" + findStack(err.Error()))
actionPtr.Object().ResponseWriter.WriteHeader(http.StatusInternalServerError)
if len(actionPtr.Object().Request.Header.Get("X-Requested-With")) > 0 {
actionPtr.Object().WriteString(serverErrString)
} else {
apiNodeIsStarting, apiNodeProgress, _, issuesHTML := parseAPIErr(actionPtr, err)
var html = `<!DOCTYPE html>
<html>
<head>
<title>正在处理...</title>
<meta charset="UTF-8"/>
<style type="text/css">
hr { border-top: 1px #ccc solid; }
.red { color: red; }
</style>
</head>
<body>
<div style="background: #eee; border: 1px #ccc solid; padding: 10px; font-size: 12px; line-height: 1.8">
`
if apiNodeIsStarting { // API节点正在启动
html += "<div class=\"red\">API节点正在启动请耐心等待完成"
if len(apiNodeProgress) > 0 {
html += "" + apiNodeProgress + "(刷新当前页面查看最新状态)"
}
html += "</div>"
} else {
html += serverErrString + `
<div>可以通过查看 <strong><em>$安装目录/logs/run.log</em></strong> 日志文件查看具体的错误提示。</div>
<hr/>
<div class="red">Error: ` + err.Error() + `</div>`
if len(issuesHTML) > 0 {
html += ` <hr/>
<div class="red">` + issuesHTML + `</div>`
}
}
actionPtr.Object().WriteString(html + `
</div>
</body>
</html>`)
}
}
// MatchPath 判断动作的文件路径是否相当
func MatchPath(action *actions.ActionObject, path string) bool {
return action.Request.URL.Path == path
}
// FindParentAction 查找父级Action
func FindParentAction(actionPtr actions.ActionWrapper) *ParentAction {
action, ok := actionPtr.(interface {
Parent() *ParentAction
})
if ok {
return action.Parent()
}
return nil
}
func findStack(err string) string {
_, currentFilename, _, currentOk := runtime.Caller(1)
if currentOk {
for i := 1; i < 32; i++ {
_, filename, lineNo, ok := runtime.Caller(i)
if !ok {
break
}
if filename == currentFilename || filepath.Base(filename) == "parent_action.go" {
continue
}
goPath := os.Getenv("GOPATH")
if len(goPath) > 0 {
absGoPath, err := filepath.Abs(goPath)
if err == nil {
filename = strings.TrimPrefix(filename, absGoPath)[1:]
}
} else if strings.Contains(filename, "src") {
filename = filename[strings.Index(filename, "src"):]
}
err += "\n\t\t" + filename + ":" + fmt.Sprintf("%d", lineNo)
break
}
}
return err
}
// 分析API节点的错误信息
func parseAPIErr(action actions.ActionWrapper, err error) (apiNodeIsStarting bool, apiNodeProgress string, isLocalAPI bool, issuesHTML string) {
// 当前API终端地址
var apiEndpoints = []string{}
apiConfig, apiConfigErr := configs.LoadAPIConfig()
if apiConfigErr == nil && apiConfig != nil {
apiEndpoints = append(apiEndpoints, apiConfig.RPCEndpoints...)
}
var isRPCConnError bool
_, isRPCConnError = rpcerrors.HumanError(err, apiEndpoints, Tea.ConfigFile(configs.ConfigFileName))
if isRPCConnError {
// API节点是否正在启动
var sock = gosock.NewTmpSock("edge-api")
reply, err := sock.SendTimeout(&gosock.Command{
Code: "starting",
Params: nil,
}, 1*time.Second)
if err == nil && reply != nil {
var params = maps.NewMap(reply.Params)
if params.GetBool("isStarting") {
apiNodeIsStarting = true
var progressMap = params.GetMap("progress")
apiNodeProgress = progressMap.GetString("description")
}
}
}
// 本地的一些错误提示
if isRPCConnError {
host, _, hostErr := net.SplitHostPort(action.Object().Request.Host)
if hostErr == nil {
for _, endpoint := range apiEndpoints {
if strings.HasPrefix(endpoint, "http://"+host) || strings.HasPrefix(endpoint, "https://"+host) || strings.HasPrefix(endpoint, host) {
isLocalAPI = true
break
}
}
}
}
if isLocalAPI {
// 读取本地API节点的issues
issuesData, issuesErr := os.ReadFile(Tea.Root + "/edge-api/logs/issues.log")
if issuesErr == nil {
var issueMaps = []maps.Map{}
issuesErr = json.Unmarshal(issuesData, &issueMaps)
if issuesErr == nil && len(issueMaps) > 0 {
var issueMap = issueMaps[0]
issuesHTML = "本地API节点启动错误" + issueMap.GetString("message") + ",处理建议:" + issueMap.GetString("suggestion")
}
}
}
return
}

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))
}
}

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