Initial commit (code only without large binaries)
This commit is contained in:
22
EdgeUser/internal/web/actions/actionutils/csrf.go
Normal file
22
EdgeUser/internal/web/actions/actionutils/csrf.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package actionutils
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/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
|
||||
}
|
||||
135
EdgeUser/internal/web/actions/actionutils/login_action.go
Normal file
135
EdgeUser/internal/web/actions/actionutils/login_action.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package actionutils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/oplogs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/login/loginutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"github.com/xlzd/gotp"
|
||||
"time"
|
||||
)
|
||||
|
||||
var TokenSalt = stringutil.Rand(32)
|
||||
|
||||
// LoginAction 登录动作
|
||||
type LoginAction struct {
|
||||
ParentAction
|
||||
}
|
||||
|
||||
// RunPost 提交
|
||||
func (this *LoginAction) RunPost(params struct {
|
||||
Token string
|
||||
Username string
|
||||
Password string
|
||||
OtpCode string
|
||||
Remember bool
|
||||
|
||||
Must *actions.Must
|
||||
Auth *helpers.UserShouldAuth
|
||||
CSRF *CSRF
|
||||
}) {
|
||||
params.Must.
|
||||
Field("username", params.Username).
|
||||
Require("请输入用户名").
|
||||
Field("password", params.Password).
|
||||
Require("请输入密码")
|
||||
|
||||
if params.Password == stringutil.Md5("") {
|
||||
this.FailField("password", "请输入密码")
|
||||
}
|
||||
|
||||
// 检查token
|
||||
if len(params.Token) <= 32 {
|
||||
this.Fail("请通过登录页面登录")
|
||||
}
|
||||
timestampString := params.Token[32:]
|
||||
if stringutil.Md5(TokenSalt+timestampString) != params.Token[:32] {
|
||||
this.FailField("refresh", "登录页面已过期,请刷新后重试")
|
||||
}
|
||||
timestamp := types.Int64(timestampString)
|
||||
if timestamp < time.Now().Unix()-1800 {
|
||||
this.FailField("refresh", "登录页面已过期,请刷新后重试")
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
this.Fail("服务器出了点小问题:" + err.Error())
|
||||
return
|
||||
}
|
||||
resp, err := rpcClient.UserRPC().LoginUser(rpcClient.Context(0), &pb.LoginUserRequest{
|
||||
Username: params.Username,
|
||||
Password: params.Password,
|
||||
})
|
||||
if err != nil {
|
||||
err = dao.SharedLogDAO.CreateUserLog(rpcClient.Context(0), oplogs.LevelError, this.Request.URL.Path, "登录时发生系统错误:"+err.Error(), loginutils.RemoteIP(&this.ActionObject))
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
}
|
||||
|
||||
Fail(this, err)
|
||||
}
|
||||
|
||||
if !resp.IsOk {
|
||||
err = dao.SharedLogDAO.CreateUserLog(rpcClient.Context(0), oplogs.LevelWarn, this.Request.URL.Path, "登录失败,用户名:"+params.Username, loginutils.RemoteIP(&this.ActionObject))
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
}
|
||||
|
||||
this.Fail(resp.Message)
|
||||
}
|
||||
|
||||
// 检查OTP
|
||||
otpLoginResp, err := this.RPC().LoginRPC().FindEnabledLogin(this.UserContext(), &pb.FindEnabledLoginRequest{
|
||||
UserId: resp.UserId,
|
||||
Type: "otp",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if otpLoginResp.Login != nil && otpLoginResp.Login.IsOn {
|
||||
loginParams := maps.Map{}
|
||||
err = json.Unmarshal(otpLoginResp.Login.ParamsJSON, &loginParams)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
secret := loginParams.GetString("secret")
|
||||
if gotp.NewDefaultTOTP(secret).Now() != params.OtpCode {
|
||||
this.Fail("请输入正确的OTP动态密码")
|
||||
}
|
||||
}
|
||||
|
||||
var userId = resp.UserId
|
||||
params.Auth.StoreUser(userId, params.Remember)
|
||||
|
||||
// 清理老的SESSION
|
||||
var currentIP = loginutils.RemoteIP(&this.ActionObject)
|
||||
_, err = this.RPC().LoginSessionRPC().ClearOldLoginSessions(this.UserContext(), &pb.ClearOldLoginSessionsRequest{
|
||||
Sid: this.Session().Sid,
|
||||
Ip: currentIP,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 记录日志
|
||||
err = dao.SharedLogDAO.CreateUserLog(rpcClient.Context(userId), oplogs.LevelInfo, this.Request.URL.Path, "成功登录系统,用户名:"+params.Username, currentIP)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
44
EdgeUser/internal/web/actions/actionutils/menu.go
Normal file
44
EdgeUser/internal/web/actions/actionutils/menu.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package actionutils
|
||||
|
||||
// 子菜单定义
|
||||
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"`
|
||||
}
|
||||
|
||||
// 获取新对象
|
||||
func NewMenu() *Menu {
|
||||
return &Menu{
|
||||
Items: []*MenuItem{},
|
||||
}
|
||||
}
|
||||
|
||||
// 添加菜单项
|
||||
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
|
||||
}
|
||||
|
||||
// 添加特殊菜单项,不计数
|
||||
func (this *Menu) AddSpecial(name string, subName string, url string, isActive bool) *MenuItem {
|
||||
item := this.Add(name, subName, url, isActive)
|
||||
this.CountNormalItems--
|
||||
return item
|
||||
}
|
||||
48
EdgeUser/internal/web/actions/actionutils/menu_group.go
Normal file
48
EdgeUser/internal/web/actions/actionutils/menu_group.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package actionutils
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
)
|
||||
|
||||
// 菜单分组
|
||||
type MenuGroup struct {
|
||||
Menus []*Menu `json:"menus"`
|
||||
AlwaysMenu *Menu `json:"alwaysMenu"`
|
||||
}
|
||||
|
||||
// 获取新菜单分组对象
|
||||
func NewMenuGroup() *MenuGroup {
|
||||
return &MenuGroup{
|
||||
Menus: []*Menu{},
|
||||
}
|
||||
}
|
||||
|
||||
// 查找菜单,如果找不到则自动创建
|
||||
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
|
||||
}
|
||||
|
||||
// 排序
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
// 设置子菜单
|
||||
func SetSubMenu(action actions.ActionWrapper, menu *MenuGroup) {
|
||||
action.Object().Data["teaSubMenus"] = menu
|
||||
}
|
||||
14
EdgeUser/internal/web/actions/actionutils/menu_item.go
Normal file
14
EdgeUser/internal/web/actions/actionutils/menu_item.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package actionutils
|
||||
|
||||
// 菜单项
|
||||
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"`
|
||||
}
|
||||
161
EdgeUser/internal/web/actions/actionutils/page.go
Normal file
161
EdgeUser/internal/web/actions/actionutils/page.go
Normal 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="height:34px;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>`
|
||||
}
|
||||
|
||||
// 判断是否为最后一页
|
||||
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
|
||||
}
|
||||
40
EdgeUser/internal/web/actions/actionutils/page_test.go
Normal file
40
EdgeUser/internal/web/actions/actionutils/page_test.go
Normal 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)
|
||||
}
|
||||
280
EdgeUser/internal/web/actions/actionutils/parent_action.go
Normal file
280
EdgeUser/internal/web/actions/actionutils/parent_action.go
Normal file
@@ -0,0 +1,280 @@
|
||||
package actionutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/nodes/serverutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/oplogs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/login/loginutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ParentAction struct {
|
||||
actions.ActionObject
|
||||
|
||||
rpcClient *rpc.RPCClient
|
||||
}
|
||||
|
||||
// Parent 可以调用自身的一个简便方法
|
||||
func (this *ParentAction) Parent() *ParentAction {
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *ParentAction) ErrorPage(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !rpc.IsConnError(err) {
|
||||
remotelogs.Error("ERROR_PAGE", this.Request.URL.String()+": "+err.Error())
|
||||
}
|
||||
|
||||
// 日志
|
||||
this.CreateLog(oplogs.LevelError, codes.UserCommon_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) {
|
||||
this.ErrorPage(errors.New(name + " id: '" + strconv.FormatInt(itemId, 10) + "' is not found"))
|
||||
}
|
||||
|
||||
func (this *ParentAction) ForbidPage() {
|
||||
this.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||
}
|
||||
|
||||
func (this *ParentAction) NewPage(total int64, size ...int64) *Page {
|
||||
if len(size) > 0 {
|
||||
return NewActionPage(this, total, size[0])
|
||||
}
|
||||
return NewActionPage(this, total, 10)
|
||||
}
|
||||
|
||||
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) UserId() int64 {
|
||||
return this.Context.GetInt64("userId")
|
||||
}
|
||||
|
||||
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.CreateUserLog(this.UserContext(), level, this.Request.URL.Path, desc, loginutils.RemoteIP(&this.ActionObject))
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ParentAction) CreateLogInfo(messageCode langs.MessageCode, args ...any) {
|
||||
this.CreateLog(oplogs.LevelInfo, messageCode, args...)
|
||||
}
|
||||
|
||||
func (this *ParentAction) LangCode() langs.LangCode {
|
||||
var langCode = this.Data.GetString("teaLang")
|
||||
if len(langCode) == 0 {
|
||||
langCode = langs.DefaultManager().DefaultLang()
|
||||
}
|
||||
return langCode
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// UserContext 获取Context
|
||||
// 每个请求的context都必须是一个新的实例
|
||||
func (this *ParentAction) UserContext() context.Context {
|
||||
if this.rpcClient == nil {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
logs.Fatal(err)
|
||||
return nil
|
||||
}
|
||||
this.rpcClient = rpcClient
|
||||
}
|
||||
return this.rpcClient.Context(this.UserId())
|
||||
}
|
||||
|
||||
// 一个不包含用户ID的上下文
|
||||
func (this *ParentAction) NullUserContext() context.Context {
|
||||
if this.rpcClient == nil {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
logs.Fatal(err)
|
||||
return nil
|
||||
}
|
||||
this.rpcClient = rpcClient
|
||||
}
|
||||
return this.rpcClient.Context(0)
|
||||
}
|
||||
|
||||
// ValidateFeature 校验Feature
|
||||
func (this *ParentAction) ValidateFeature(featureCode string, serverId int64) bool {
|
||||
b, err := this.validateFeature(featureCode, serverId)
|
||||
if err != nil {
|
||||
remotelogs.Error("FEATURE", "validate feature: "+err.Error())
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (this *ParentAction) validateFeature(featureCode string, serverId int64) (bool, error) {
|
||||
// 检查审核和认证
|
||||
if configloaders.RequireVerification() && !this.Context.GetBool("isVerified") {
|
||||
return false, nil
|
||||
}
|
||||
if configloaders.RequireIdentity() && !this.Context.GetBool("isIdentified") {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var featureDef = userconfigs.FindUserFeature(featureCode)
|
||||
if featureDef == nil {
|
||||
return false, errors.New("invalid feature code '" + featureCode + "'")
|
||||
}
|
||||
|
||||
// 检查网站绑定的套餐
|
||||
if serverId > 0 && featureDef.SupportPlan {
|
||||
userPlanResp, err := this.RPC().ServerRPC().FindServerUserPlan(this.UserContext(), &pb.FindServerUserPlanRequest{ServerId: serverId})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if userPlanResp.UserPlan != nil && userPlanResp.UserPlan.IsOn && userPlanResp.UserPlan.Plan != nil /** 不需要判断套餐是否启用(即不需要判断是否为UserPlan.Plan.isOn),以便于兼容套餐被删除的情况 **/ {
|
||||
var plan = userPlanResp.UserPlan.Plan
|
||||
if plan.HasFullFeatures {
|
||||
return true, nil
|
||||
}
|
||||
var supportedFeatureCodes []string
|
||||
if len(plan.FeaturesJSON) > 0 {
|
||||
err = json.Unmarshal(plan.FeaturesJSON, &supportedFeatureCodes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return lists.ContainsString(supportedFeatureCodes, featureCode), nil
|
||||
}
|
||||
}
|
||||
|
||||
// 用户功能
|
||||
userFeatureResp, err := this.RPC().UserRPC().FindUserFeatures(this.UserContext(), &pb.FindUserFeaturesRequest{UserId: this.UserId()})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var userFeatureCodes = []string{}
|
||||
for _, feature := range userFeatureResp.Features {
|
||||
userFeatureCodes = append(userFeatureCodes, feature.Code)
|
||||
}
|
||||
|
||||
return lists.ContainsString(userFeatureCodes, featureCode), nil
|
||||
}
|
||||
|
||||
func (this *ParentAction) CheckUserStatus() bool {
|
||||
// 审核和认证
|
||||
if configloaders.RequireVerification() && !this.Context.GetBool("isVerified") {
|
||||
this.RedirectURL("/")
|
||||
return false
|
||||
}
|
||||
if configloaders.RequireIdentity() && !this.Context.GetBool("isIdentified") {
|
||||
this.RedirectURL("/")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *ParentAction) FindServerIdWithWebId(webId int64) (serverId int64, err error) {
|
||||
serverIdResp, err := this.RPC().HTTPWebRPC().FindServerIdWithHTTPWebId(this.UserContext(), &pb.FindServerIdWithHTTPWebIdRequest{
|
||||
HttpWebId: webId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
serverId = serverIdResp.ServerId
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ParentAction) CheckHTTPSRedirecting() {
|
||||
if this.Request.TLS == nil {
|
||||
httpsPort, _ := serverutils.ReadServerHTTPS()
|
||||
if httpsPort > 0 {
|
||||
currentHost, _, hostErr := net.SplitHostPort(this.Request.Host)
|
||||
if hostErr != nil {
|
||||
currentHost = this.Request.Host
|
||||
}
|
||||
|
||||
var newHost = configutils.QuoteIP(currentHost)
|
||||
if httpsPort != 443 /** default https port **/ {
|
||||
newHost += ":" + types.String(httpsPort)
|
||||
}
|
||||
|
||||
// 如果没有前端反向代理,则跳转
|
||||
if len(this.Request.Header.Get("X-Forwarded-For")) == 0 && len(this.Request.Header.Get("X-Real-Ip")) == 0 {
|
||||
this.RedirectURL("https://" + newHost + this.Request.RequestURI)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package actionutils
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils/sizes"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"io"
|
||||
)
|
||||
|
||||
func (this *ParentAction) UploadFile(formFile *actions.File, businessType string) (fileId int64, ok bool, err error) {
|
||||
file, err := formFile.OriginFile.Open()
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
fileResp, err := this.RPC().FileRPC().CreateFile(this.UserContext(), &pb.CreateFileRequest{
|
||||
Filename: formFile.Filename,
|
||||
Size: formFile.Size,
|
||||
IsPublic: false,
|
||||
MimeType: formFile.ContentType,
|
||||
Type: businessType,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
fileId = fileResp.FileId
|
||||
|
||||
var buf = make([]byte, 128*sizes.K) // TODO 使用pool管理
|
||||
|
||||
for {
|
||||
n, err := file.Read(buf)
|
||||
if n > 0 {
|
||||
_, chunkErr := this.RPC().FileChunkRPC().CreateFileChunk(this.UserContext(), &pb.CreateFileChunkRequest{
|
||||
FileId: fileId,
|
||||
Data: buf[:n],
|
||||
})
|
||||
if chunkErr != nil {
|
||||
return 0, false, chunkErr
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return 0, false, nil
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_, err = this.RPC().FileRPC().UpdateFileFinished(this.UserContext(), &pb.UpdateFileFinishedRequest{
|
||||
FileId: fileId,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
return fileId, true, nil
|
||||
}
|
||||
88
EdgeUser/internal/web/actions/actionutils/portal_action.go
Normal file
88
EdgeUser/internal/web/actions/actionutils/portal_action.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package actionutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/configloaders"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net/http"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var faviconReg = regexp.MustCompile(`<link\s+rel="icon"[^>]+>`)
|
||||
|
||||
type PortalAction struct {
|
||||
ParentAction
|
||||
}
|
||||
|
||||
func (this *PortalAction) IsFrontRequest() bool {
|
||||
return len(this.Request.URL.Query().Get("X_FROM_FRONT")) > 0
|
||||
}
|
||||
|
||||
func (this *PortalAction) Success() {
|
||||
if this.IsFrontRequest() {
|
||||
this.prepareCORSHeader()
|
||||
}
|
||||
this.ActionObject.Success()
|
||||
}
|
||||
|
||||
func (this *PortalAction) Show() {
|
||||
if this.IsFrontRequest() {
|
||||
this.prepareCORSHeader()
|
||||
this.ActionObject.Success()
|
||||
return
|
||||
}
|
||||
|
||||
// add header
|
||||
this.ViewFunc("X_VIEW_DATA", func() string {
|
||||
if this.Data == nil {
|
||||
return ""
|
||||
}
|
||||
return string(this.Data.AsJSON())
|
||||
})
|
||||
this.ViewFunc("X_VIEW_FAVICON", func() string {
|
||||
uiConfig, err := configloaders.LoadUIConfig()
|
||||
if err != nil || uiConfig == nil {
|
||||
return ""
|
||||
}
|
||||
if uiConfig.FaviconFileId > 0 {
|
||||
return `<link rel="icon" href="/ui/image/` + types.String(uiConfig.FaviconFileId) + `"/>`
|
||||
}
|
||||
return ""
|
||||
})
|
||||
|
||||
this.TemplateFilter(func(body []byte) []byte {
|
||||
// head
|
||||
body = bytes.ReplaceAll(body, []byte("</head>"), []byte(`
|
||||
<script>
|
||||
window.X_FROM_SERVER = true;
|
||||
window.X_VIEW_DATA = {$ X_VIEW_DATA };
|
||||
</script>
|
||||
</head>`))
|
||||
|
||||
// favicon
|
||||
body = faviconReg.ReplaceAll(body, []byte(`{$ X_VIEW_FAVICON }`))
|
||||
|
||||
return body
|
||||
})
|
||||
|
||||
this.ViewDir(Tea.Root)
|
||||
this.ActionObject.Show()
|
||||
}
|
||||
|
||||
func (this *PortalAction) Redirect(url string, status int) {
|
||||
if this.IsFrontRequest() {
|
||||
this.Data["X_FRONT_REDIRECT"] = maps.Map{"url": url}
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
http.Redirect(this.ResponseWriter, this.Request, url, status)
|
||||
}
|
||||
|
||||
func (this *PortalAction) prepareCORSHeader() {
|
||||
this.ResponseWriter.Header().Set("Access-Control-Allow-Origin", this.Request.Header.Get("Origin"))
|
||||
this.ResponseWriter.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
42
EdgeUser/internal/web/actions/actionutils/tabbar.go
Normal file
42
EdgeUser/internal/web/actions/actionutils/tabbar.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package actionutils
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// Tabbar定义
|
||||
type Tabbar struct {
|
||||
items []maps.Map
|
||||
}
|
||||
|
||||
// 获取新对象
|
||||
func NewTabbar() *Tabbar {
|
||||
return &Tabbar{
|
||||
items: []maps.Map{},
|
||||
}
|
||||
}
|
||||
|
||||
// 添加菜单项
|
||||
func (this *Tabbar) Add(name string, subName string, url string, icon string, active bool) maps.Map {
|
||||
m := maps.Map{
|
||||
"name": name,
|
||||
"subName": subName,
|
||||
"url": url,
|
||||
"icon": icon,
|
||||
"active": active,
|
||||
"right": false,
|
||||
}
|
||||
this.items = append(this.items, m)
|
||||
return m
|
||||
}
|
||||
|
||||
// 取得所有的Items
|
||||
func (this *Tabbar) Items() []maps.Map {
|
||||
return this.items
|
||||
}
|
||||
|
||||
// 设置子菜单
|
||||
func SetTabbar(action actions.ActionWrapper, tabbar *Tabbar) {
|
||||
action.Object().Data["teaTabbar"] = tabbar.Items()
|
||||
}
|
||||
116
EdgeUser/internal/web/actions/actionutils/utils.go
Normal file
116
EdgeUser/internal/web/actions/actionutils/utils.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package actionutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
rpcerrors "github.com/TeaOSLab/EdgeCommon/pkg/rpc/errors"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeUser/internal/const"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Fail 提示服务器错误信息
|
||||
func Fail(action actions.ActionWrapper, err error) {
|
||||
if err != nil {
|
||||
logs.Println("[" + reflect.TypeOf(action).String() + "]" + findStack(err.Error()))
|
||||
}
|
||||
action.Object().Fail(teaconst.ErrServer + "(请查看日志文件以获取具体信息)")
|
||||
}
|
||||
|
||||
// FailPage 提示页面错误信息
|
||||
func FailPage(action actions.ActionWrapper, err error) {
|
||||
if err == nil {
|
||||
err = errors.New("unknown error")
|
||||
}
|
||||
|
||||
logs.Println("[" + reflect.TypeOf(action).String() + "]" + findStack(err.Error()))
|
||||
|
||||
// 当前API终端地址
|
||||
var apiEndpoints = []string{}
|
||||
apiConfig, apiConfigErr := configs.LoadAPIConfig()
|
||||
if apiConfigErr == nil && apiConfig != nil {
|
||||
apiEndpoints = append(apiEndpoints, apiConfig.RPCEndpoints...)
|
||||
}
|
||||
|
||||
err, _ = rpcerrors.HumanError(err, apiEndpoints, Tea.ConfigFile(configs.ConfigFileName))
|
||||
action.Object().ResponseWriter.WriteHeader(http.StatusInternalServerError)
|
||||
if len(action.Object().Request.Header.Get("X-Requested-With")) > 0 {
|
||||
action.Object().WriteString(teaconst.ErrServer)
|
||||
} else {
|
||||
action.Object().WriteString(`<!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">
|
||||
` + teaconst.ErrServer + `
|
||||
<div>可以通过查看日志文件查看具体的错误提示。</div>
|
||||
<hr/>
|
||||
<div class="red">Error: ` + err.Error() + `</div>
|
||||
</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 {
|
||||
parentActionValue, ok := actionPtr.(interface {
|
||||
Parent() *ParentAction
|
||||
})
|
||||
if ok {
|
||||
return parentActionValue.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
|
||||
}
|
||||
12
EdgeUser/internal/web/actions/default/account/init.go
Normal file
12
EdgeUser/internal/web/actions/default/account/init.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package account
|
||||
|
||||
import "github.com/iwind/TeaGo"
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Prefix("/account").
|
||||
GetPost("/reset", new(ResetAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
161
EdgeUser/internal/web/actions/default/account/reset.go
Normal file
161
EdgeUser/internal/web/actions/default/account/reset.go
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package account
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/configloaders"
|
||||
teaconst "github.com/TeaOSLab/EdgeUser/internal/const"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ResetAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *ResetAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *ResetAction) RunGet(params struct{}) {
|
||||
// 界面
|
||||
config, err := configloaders.LoadUIConfig()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["systemName"] = config.UserSystemName
|
||||
this.Data["showVersion"] = config.ShowVersion
|
||||
if len(config.Version) > 0 {
|
||||
this.Data["version"] = config.Version
|
||||
} else {
|
||||
this.Data["version"] = teaconst.Version
|
||||
}
|
||||
this.Data["faviconFileId"] = config.FaviconFileId
|
||||
|
||||
// 密码强度
|
||||
registerConfig, err := configloaders.LoadRegisterConfig()
|
||||
if err != nil {
|
||||
this.Fail("暂未开放注册功能")
|
||||
}
|
||||
this.Data["complexPassword"] = registerConfig.ComplexPassword
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *ResetAction) RunPost(params struct {
|
||||
Action string
|
||||
UsernameOrEmail string
|
||||
|
||||
Code string
|
||||
NewPass string
|
||||
NewPass2 string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
// 检查用户名或邮箱是否存在
|
||||
if len(params.UsernameOrEmail) == 0 {
|
||||
this.FailField("usernameOrEmail", "请输入用户名或已经绑定的邮箱")
|
||||
return
|
||||
}
|
||||
|
||||
var email string
|
||||
if strings.Contains(params.UsernameOrEmail, "@") { // 邮箱
|
||||
checkResp, err := this.RPC().UserRPC().CheckUserEmail(this.UserContext(), &pb.CheckUserEmailRequest{Email: params.UsernameOrEmail})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if !checkResp.Exists {
|
||||
this.FailField("usernameOrEmail", "找不到和此邮箱绑定的用户")
|
||||
return
|
||||
}
|
||||
email = params.UsernameOrEmail
|
||||
} else { // 用户名
|
||||
checkResp, err := this.RPC().UserRPC().CheckUserUsername(this.UserContext(), &pb.CheckUserUsernameRequest{
|
||||
UserId: 0,
|
||||
Username: params.UsernameOrEmail,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if !checkResp.Exists {
|
||||
this.FailField("usernameOrEmail", "此用户名尚未注册")
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否已绑定邮箱
|
||||
emailResp, err := this.RPC().UserRPC().FindUserVerifiedEmailWithUsername(this.UserContext(), &pb.FindUserVerifiedEmailWithUsernameRequest{Username: params.UsernameOrEmail})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if len(emailResp.Email) == 0 {
|
||||
this.FailField("usernameOrEmail", "此用户尚未绑定邮箱,不能通过邮箱找回密码")
|
||||
return
|
||||
}
|
||||
|
||||
email = emailResp.Email
|
||||
}
|
||||
|
||||
// 发送验证码
|
||||
if params.Action == "send" {
|
||||
_, err := this.RPC().UserVerifyCodeRPC().SendUserVerifyCode(this.UserContext(), &pb.SendUserVerifyCodeRequest{
|
||||
Type: "resetPassword",
|
||||
Email: email,
|
||||
Mobile: "", // TODO 将来实现通过手机号找回
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["email"] = email
|
||||
}
|
||||
|
||||
// 修改密码
|
||||
if params.Action == "update" {
|
||||
// 密码
|
||||
params.Must.
|
||||
Field("newPass", params.NewPass).
|
||||
Require("请输入新登录密码").
|
||||
MinLength(6, "登录密码长度不能小于6位")
|
||||
|
||||
registerConfig, err := configloaders.LoadRegisterConfig()
|
||||
if err == nil {
|
||||
if registerConfig.ComplexPassword && (!regexp.MustCompile(`[a-z]`).MatchString(params.NewPass) || !regexp.MustCompile(`[A-Z]`).MatchString(params.NewPass)) {
|
||||
this.FailField("newPass", "为了您的账号安全,密码中必须包含大写和小写字母")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if params.NewPass != params.NewPass2 {
|
||||
this.FailField("newPass2", "两次输入的密码不一致")
|
||||
return
|
||||
}
|
||||
|
||||
// 校验验证码
|
||||
validateResp, err := this.RPC().UserVerifyCodeRPC().ValidateUserVerifyCode(this.UserContext(), &pb.ValidateUserVerifyCodeRequest{
|
||||
Type: "resetPassword",
|
||||
Email: email,
|
||||
Mobile: "", // TODO 将来实现
|
||||
Code: params.Code,
|
||||
NewPassword: params.NewPass,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if !validateResp.IsOk {
|
||||
this.Fail("验证失败:" + validateResp.ErrorMessage)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package accesskeys
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"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 {
|
||||
Description string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.
|
||||
Field("description", params.Description).
|
||||
Require("请输入备注")
|
||||
|
||||
accessKeyIdResp, err := this.RPC().UserAccessKeyRPC().CreateUserAccessKey(this.UserContext(), &pb.CreateUserAccessKeyRequest{
|
||||
UserId: this.UserId(),
|
||||
Description: params.Description,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer this.CreateLogInfo(codes.UserAccessKey_LogCreateUserAccessKey, accessKeyIdResp.UserAccessKeyId)
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package accesskeys
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
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.UserContext(), &pb.DeleteUserAccessKeyRequest{UserAccessKeyId: params.AccessKeyId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package accesskeys
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
accessKeysResp, err := this.RPC().UserAccessKeyRPC().FindAllEnabledUserAccessKeys(this.UserContext(), &pb.FindAllEnabledUserAccessKeysRequest{UserId: this.UserId()})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
accessKeyMaps := []maps.Map{}
|
||||
for _, accessKey := range accessKeysResp.UserAccessKeys {
|
||||
accessKeyMaps = append(accessKeyMaps, maps.Map{
|
||||
"id": accessKey.Id,
|
||||
"isOn": accessKey.IsOn,
|
||||
"uniqueId": accessKey.UniqueId,
|
||||
"secret": accessKey.Secret,
|
||||
"description": accessKey.Description,
|
||||
})
|
||||
}
|
||||
this.Data["accessKeys"] = accessKeyMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
20
EdgeUser/internal/web/actions/default/acl/accesskeys/init.go
Normal file
20
EdgeUser/internal/web/actions/default/acl/accesskeys/init.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package accesskeys
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth("")).
|
||||
Data("teaMenu", "acl").
|
||||
Prefix("/acl/accesskeys").
|
||||
Get("", new(IndexAction)).
|
||||
GetPost("/createPopup", new(CreatePopupAction)).
|
||||
Post("/delete", new(DeleteAction)).
|
||||
Post("/updateIsOn", new(UpdateIsOnAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package accesskeys
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
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.UserContext(), &pb.UpdateUserAccessKeyIsOnRequest{
|
||||
UserAccessKeyId: params.AccessKeyId,
|
||||
IsOn: params.IsOn,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
17
EdgeUser/internal/web/actions/default/acl/index.go
Normal file
17
EdgeUser/internal/web/actions/default/acl/index.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
this.RedirectURL("/acl/accesskeys")
|
||||
}
|
||||
17
EdgeUser/internal/web/actions/default/acl/init.go
Normal file
17
EdgeUser/internal/web/actions/default/acl/init.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth("")).
|
||||
Data("teaMenu", "acl").
|
||||
Prefix("/acl").
|
||||
Get("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
17
EdgeUser/internal/web/actions/default/anti-ddos/index.go
Normal file
17
EdgeUser/internal/web/actions/default/anti-ddos/index.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package antiddos
|
||||
|
||||
import "github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
this.RedirectURL("/anti-ddos/instances")
|
||||
}
|
||||
40
EdgeUser/internal/web/actions/default/anti-ddos/init.go
Normal file
40
EdgeUser/internal/web/actions/default/anti-ddos/init.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package antiddos
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/anti-ddos/instances"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/anti-ddos/packages"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth("")).
|
||||
Data("teaMenu", "anti-ddos").
|
||||
Prefix("/anti-ddos").
|
||||
Get("", new(IndexAction)).
|
||||
|
||||
// 实例
|
||||
Prefix("/anti-ddos/instances").
|
||||
Data("teaSubMenu", "instance").
|
||||
Get("", new(instances.IndexAction)).
|
||||
Post("/delete", new(instances.DeleteAction)).
|
||||
GetPost("/updateObjectsPopup", new(instances.UpdateObjectsPopupAction)).
|
||||
Post("/userServers", new(instances.UserServersAction)).
|
||||
GetPost("/renew", new(instances.RenewAction)).
|
||||
GetPost("/renewConfirm", new(instances.RenewConfirmAction)).
|
||||
|
||||
// 产品
|
||||
Prefix("/anti-ddos/packages").
|
||||
Data("teaSubMenu", "package").
|
||||
Get("", new(packages.IndexAction)).
|
||||
Post("/price", new(packages.PriceAction)).
|
||||
GetPost("/confirm", new(packages.ConfirmAction)).
|
||||
|
||||
//
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package instances
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
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.UserContext(), &pb.DeleteUserADInstanceRequest{UserADInstanceId: params.UserInstanceId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package instances
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils/dateutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"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 {
|
||||
NetworkId int64
|
||||
PeriodId int64
|
||||
}) {
|
||||
countResp, err := this.RPC().UserADInstanceRPC().CountUserADInstances(this.UserContext(), &pb.CountUserADInstancesRequest{
|
||||
AdNetworkId: params.NetworkId,
|
||||
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.UserContext(), &pb.ListUserADInstancesRequest{
|
||||
AdNetworkId: params.NetworkId,
|
||||
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 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),
|
||||
"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": true,
|
||||
"isExpired": userInstance.DayTo < timeutil.Format("Ymd"),
|
||||
"isAvailable": userInstance.IsAvailable,
|
||||
"countObjects": userInstance.CountObjects,
|
||||
})
|
||||
}
|
||||
this.Data["userInstances"] = userInstanceMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package instances
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils/dateutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
type RenewAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *RenewAction) RunGet(params struct {
|
||||
UserInstanceId int64
|
||||
}) {
|
||||
userInstanceResp, err := this.RPC().UserADInstanceRPC().FindUserADInstance(this.UserContext(), &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 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,
|
||||
"instance": instanceMap,
|
||||
"package": packageMap,
|
||||
}
|
||||
|
||||
// 价格选项
|
||||
if packageId > 0 {
|
||||
pricesResp, err := this.RPC().ADPackagePriceRPC().FindADPackagePrices(this.UserContext(), &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.UserContext(), &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()
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package instances
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils/dateutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/finance/financeutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
)
|
||||
|
||||
type RenewConfirmAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *RenewConfirmAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *RenewConfirmAction) RunGet(params struct {
|
||||
UserInstanceId int64
|
||||
PeriodId int64
|
||||
}) {
|
||||
this.Data["userInstanceId"] = params.UserInstanceId
|
||||
this.Data["periodId"] = params.PeriodId
|
||||
|
||||
periodResp, err := this.RPC().ADPackagePeriodRPC().FindADPackagePeriod(this.UserContext(), &pb.FindADPackagePeriodRequest{AdPackagePeriodId: params.PeriodId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var period = periodResp.AdPackagePeriod
|
||||
if period == nil {
|
||||
this.ErrorText("period not found")
|
||||
return
|
||||
}
|
||||
this.Data["periodName"] = types.String(period.Count) + userconfigs.ADPackagePeriodUnitName(period.Unit)
|
||||
|
||||
userInstanceResp, err := this.RPC().UserADInstanceRPC().FindUserADInstance(this.UserContext(), &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
|
||||
}
|
||||
this.Data["dayTo"] = dateutils.SplitYmd(userInstance.DayTo)
|
||||
|
||||
// 实例
|
||||
if userInstance.AdPackageInstance == nil {
|
||||
this.ErrorText("adPackageInstance not found")
|
||||
return
|
||||
}
|
||||
if userInstance.AdPackageInstance.IpAddresses == nil {
|
||||
userInstance.AdPackageInstance.IpAddresses = []string{}
|
||||
}
|
||||
this.Data["ipAddresses"] = userInstance.AdPackageInstance.IpAddresses
|
||||
|
||||
// 产品
|
||||
if userInstance.AdPackageInstance.AdPackage == nil {
|
||||
this.ErrorText("adPackage not found")
|
||||
return
|
||||
}
|
||||
var packageId = userInstance.AdPackageInstance.AdPackage.Id
|
||||
this.Data["packageSummary"] = userInstance.AdPackageInstance.AdPackage.Summary
|
||||
|
||||
// 价格
|
||||
priceResp, err := this.RPC().ADPackagePriceRPC().FindADPackagePrice(this.UserContext(), &pb.FindADPackagePriceRequest{
|
||||
AdPackageId: packageId,
|
||||
AdPackagePeriodId: params.PeriodId,
|
||||
Count: 1,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["amount"] = priceResp.Amount
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *RenewConfirmAction) RunPost(params struct {
|
||||
UserInstanceId int64
|
||||
PeriodId int64
|
||||
|
||||
MethodCode string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
if params.UserInstanceId <= 0 || params.PeriodId <= 0 {
|
||||
this.Fail("参数错误,请重新下单")
|
||||
return
|
||||
}
|
||||
|
||||
userInstanceResp, err := this.RPC().UserADInstanceRPC().FindUserADInstance(this.UserContext(), &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
|
||||
}
|
||||
|
||||
if userInstance.AdPackageInstance == nil {
|
||||
this.ErrorText("invalid adPackageInstance")
|
||||
return
|
||||
}
|
||||
var packageId = userInstance.AdPackageInstance.AdPackageId
|
||||
if !userInstance.IsAvailable {
|
||||
this.ErrorText("invalid user instance")
|
||||
return
|
||||
}
|
||||
|
||||
periodResp, err := this.RPC().ADPackagePeriodRPC().FindADPackagePeriod(this.UserContext(), &pb.FindADPackagePeriodRequest{AdPackagePeriodId: params.PeriodId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var period = periodResp.AdPackagePeriod
|
||||
if period == nil {
|
||||
this.Fail("invalid 'periodId'")
|
||||
return
|
||||
}
|
||||
|
||||
priceResp, err := this.RPC().ADPackagePriceRPC().FindADPackagePrice(this.UserContext(), &pb.FindADPackagePriceRequest{
|
||||
AdPackageId: packageId,
|
||||
AdPackagePeriodId: params.PeriodId,
|
||||
Count: 1,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var amount = priceResp.Amount
|
||||
if amount <= 0 {
|
||||
this.Fail("invalid 'amount': " + types.String(amount))
|
||||
return
|
||||
}
|
||||
|
||||
// 使用余额购买
|
||||
this.Data["success"] = false
|
||||
this.Data["orderCode"] = ""
|
||||
if params.MethodCode == "@balance" {
|
||||
balance, err := financeutils.FindUserBalance(this.UserContext())
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if amount > balance {
|
||||
this.Fail("当前余额不足,需要:" + fmt.Sprintf("%.2f元", amount) + ",现有:" + fmt.Sprintf("%.2f元", balance) + " ,请充值后再试")
|
||||
return
|
||||
}
|
||||
|
||||
// 直接购买
|
||||
_, err = this.RPC().UserADInstanceRPC().RenewUserADInstance(this.UserContext(), &pb.RenewUserADInstanceRequest{
|
||||
UserADInstanceId: params.UserInstanceId,
|
||||
AdPackagePeriodId: params.PeriodId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["success"] = true
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
// 生成订单
|
||||
var orderParams = &userconfigs.OrderTypeRenewAntiDDoSInstanceParams{
|
||||
UserInstanceId: params.UserInstanceId,
|
||||
PeriodId: params.PeriodId,
|
||||
}
|
||||
orderParamsJSON, err := json.Marshal(orderParams)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := this.RPC().UserOrderRPC().CreateUserOrder(this.UserContext(), &pb.CreateUserOrderRequest{
|
||||
Type: userconfigs.OrderTypeRenewAntiDDoSInstance,
|
||||
OrderMethodCode: params.MethodCode,
|
||||
Amount: amount,
|
||||
ParamsJSON: orderParamsJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["orderCode"] = resp.Code
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package instances
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils/dateutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"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.UserContext(), &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.UserContext(), &pb.UpdateUserADInstanceObjectsRequest{
|
||||
UserADInstanceId: params.UserInstanceId,
|
||||
ObjectCodes: objectCodes,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package instances
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type UserServersAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UserServersAction) RunPost(params struct {
|
||||
Page int32
|
||||
}) {
|
||||
var size int64 = 10
|
||||
|
||||
// 数量
|
||||
countResp, err := this.RPC().ServerRPC().CountAllEnabledServersMatch(this.UserContext(), &pb.CountAllEnabledServersMatchRequest{
|
||||
UserId: this.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.UserContext(), &pb.ListEnabledServersMatchRequest{
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
UserId: this.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()
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/finance/financeutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
)
|
||||
|
||||
type ConfirmAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *ConfirmAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *ConfirmAction) RunGet(params struct {
|
||||
PackageId int64
|
||||
PeriodId int64
|
||||
Count int32
|
||||
}) {
|
||||
this.Data["packageId"] = params.PackageId
|
||||
this.Data["periodId"] = params.PeriodId
|
||||
this.Data["count"] = params.Count
|
||||
|
||||
this.Data["packageSummary"] = ""
|
||||
this.Data["periodName"] = ""
|
||||
this.Data["amount"] = 0
|
||||
|
||||
if params.PackageId <= 0 || params.PeriodId <= 0 || params.Count <= 0 {
|
||||
this.Show()
|
||||
return
|
||||
}
|
||||
|
||||
// 产品信息
|
||||
packageResp, err := this.RPC().ADPackageRPC().FindADPackage(this.UserContext(), &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["packageSummary"] = adPackage.Summary
|
||||
|
||||
// 日期信息
|
||||
periodResp, err := this.RPC().ADPackagePeriodRPC().FindADPackagePeriod(this.UserContext(), &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["periodName"] = types.String(period.Count) + userconfigs.ADPackagePeriodUnitName(period.Unit)
|
||||
|
||||
// 数量
|
||||
countInstancesResp, err := this.RPC().ADPackageInstanceRPC().CountIdleADPackageInstances(this.UserContext(), &pb.CountIdleADPackageInstancesRequest{AdPackageId: params.PackageId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var countInstances = types.Int32(countInstancesResp.Count)
|
||||
if countInstances < params.Count {
|
||||
this.Show()
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := this.RPC().ADPackagePriceRPC().FindADPackagePrice(this.UserContext(), &pb.FindADPackagePriceRequest{
|
||||
AdPackageId: params.PackageId,
|
||||
AdPackagePeriodId: params.PeriodId,
|
||||
Count: params.Count,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["amount"] = resp.Amount
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *ConfirmAction) RunPost(params struct {
|
||||
PackageId int64
|
||||
PeriodId int64
|
||||
Count int32
|
||||
|
||||
MethodCode string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
if params.PackageId <= 0 || params.PeriodId <= 0 || params.Count <= 0 {
|
||||
this.Fail("参数错误,请重新下单")
|
||||
return
|
||||
}
|
||||
|
||||
periodResp, err := this.RPC().ADPackagePeriodRPC().FindADPackagePeriod(this.UserContext(), &pb.FindADPackagePeriodRequest{AdPackagePeriodId: params.PeriodId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var period = periodResp.AdPackagePeriod
|
||||
if period == nil {
|
||||
this.Fail("invalid 'periodId'")
|
||||
return
|
||||
}
|
||||
|
||||
priceResp, err := this.RPC().ADPackagePriceRPC().FindADPackagePrice(this.UserContext(), &pb.FindADPackagePriceRequest{
|
||||
AdPackageId: params.PackageId,
|
||||
AdPackagePeriodId: params.PeriodId,
|
||||
Count: params.Count,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var amount = priceResp.Amount
|
||||
|
||||
// 使用余额购买
|
||||
this.Data["success"] = false
|
||||
this.Data["orderCode"] = ""
|
||||
if params.MethodCode == "@balance" {
|
||||
balance, err := financeutils.FindUserBalance(this.UserContext())
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if amount > balance {
|
||||
this.Fail("当前余额不足,需要:" + fmt.Sprintf("%.2f元", amount) + ",现有:" + fmt.Sprintf("%.2f元", balance) + " ,请充值后再试")
|
||||
return
|
||||
}
|
||||
|
||||
// 直接购买
|
||||
_, err = this.RPC().UserADInstanceRPC().BuyUserADInstance(this.UserContext(), &pb.BuyUserADInstanceRequest{
|
||||
UserId: this.UserId(),
|
||||
AdPackageId: params.PackageId,
|
||||
AdPackagePeriodId: params.PeriodId,
|
||||
Count: params.Count,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["success"] = true
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
// 生成订单
|
||||
var orderParams = &userconfigs.OrderTypeBuyAntiDDoSInstanceParams{
|
||||
PackageId: params.PackageId,
|
||||
PeriodId: params.PeriodId,
|
||||
PeriodCount: period.Count,
|
||||
PeriodUnit: period.Unit,
|
||||
Count: params.Count,
|
||||
}
|
||||
orderParamsJSON, err := json.Marshal(orderParams)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := this.RPC().UserOrderRPC().CreateUserOrder(this.UserContext(), &pb.CreateUserOrderRequest{
|
||||
Type: userconfigs.OrderTypeBuyAntiDDoSInstance,
|
||||
OrderMethodCode: params.MethodCode,
|
||||
Amount: amount,
|
||||
ParamsJSON: orderParamsJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["orderCode"] = resp.Code
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
// 高防产品
|
||||
var allPackageMap = map[int64]*pb.ADPackage{} // packageId => *pb.ADPackage
|
||||
packagesResp, err := this.RPC().ADPackageRPC().FindAllIdleADPackages(this.UserContext(), &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.UserContext(), &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.UserContext(), &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.UserContext(), &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()
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package packages
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"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.UserContext(), &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.UserContext(), &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()
|
||||
}
|
||||
1
EdgeUser/internal/web/actions/default/api/README.md
Normal file
1
EdgeUser/internal/web/actions/default/api/README.md
Normal file
@@ -0,0 +1 @@
|
||||
支付、通知等URL
|
||||
30
EdgeUser/internal/web/actions/default/api/init.go
Normal file
30
EdgeUser/internal/web/actions/default/api/init.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/api/pay/alipay"
|
||||
serversapi "github.com/TeaOSLab/EdgeUser/internal/web/actions/default/api/servers"
|
||||
domainsapi "github.com/TeaOSLab/EdgeUser/internal/web/actions/default/api/servers/domains"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Prefix("/api").
|
||||
|
||||
// pay
|
||||
Post("/pay/alipay/notify", new(alipay.NotifyAction)).
|
||||
|
||||
// servers
|
||||
Helper(helpers.NewUserMustAuth("")).
|
||||
Post("/user/servers/list", new(serversapi.ServersListAction)).
|
||||
Post("/user/servers/domains/add", new(domainsapi.DomainsAddAction)).
|
||||
Post("/user/servers/domains/delete", new(domainsapi.DomainsDeleteAction)).
|
||||
|
||||
//
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package alipay
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/login/loginutils"
|
||||
)
|
||||
|
||||
// NotifyAction TODO 防止cc攻击
|
||||
type NotifyAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *NotifyAction) RunPost(params struct{}) {
|
||||
formData, err := json.Marshal(this.Request.Form)
|
||||
if err != nil {
|
||||
// 提示中加入IP方便调试
|
||||
remotelogs.Error("ALIPAY", "["+loginutils.RemoteIP(&this.ActionObject)+"]NotifyAction: encode form failed: "+err.Error())
|
||||
this.Fail(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 接口会自动根据传输的订单号决定使用哪种支付方式验证
|
||||
_, err = this.RPC().UserOrderRPC().NotifyUserOrderPayment(this.UserContext(), &pb.NotifyUserOrderPaymentRequest{
|
||||
PayMethod: userconfigs.PayMethodAlipay,
|
||||
FormData: formData,
|
||||
})
|
||||
if err != nil {
|
||||
// 提示中加入IP方便调试
|
||||
remotelogs.Error("ALIPAY", "["+loginutils.RemoteIP(&this.ActionObject)+"]NotifyAction: verify failed: "+err.Error())
|
||||
this.Fail(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
280
EdgeUser/internal/web/actions/default/api/servers/domains/add.go
Normal file
280
EdgeUser/internal/web/actions/default/api/servers/domains/add.go
Normal file
@@ -0,0 +1,280 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
type DomainsAddAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *DomainsAddAction) RunPost(params struct{}) {
|
||||
// 获取当前登录用户ID
|
||||
userId := this.UserId()
|
||||
if userId <= 0 {
|
||||
this.writeJSON(http.StatusUnauthorized, "请先登录", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 手动读取JSON请求体
|
||||
body, err := io.ReadAll(this.Request.Body)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusBadRequest, "读取请求体失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析JSON参数
|
||||
var requestParams struct {
|
||||
ServerId int64 `json:"serverId"`
|
||||
Domains []string `json:"domains"`
|
||||
}
|
||||
err = json.Unmarshal(body, &requestParams)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusBadRequest, "解析请求参数失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 参数验证
|
||||
if requestParams.ServerId <= 0 {
|
||||
this.writeJSON(http.StatusBadRequest, "serverId不能为空", nil)
|
||||
return
|
||||
}
|
||||
if len(requestParams.Domains) == 0 {
|
||||
this.writeJSON(http.StatusBadRequest, "domains不能为空", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 注意:权限验证由 UpdateServerWebConfigForUser 内部完成,这里不需要单独调用 CheckUserServer
|
||||
// 获取Access Token
|
||||
accessToken, err := this.getAccessToken(userId)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "获取访问令牌失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 通过HTTP调用EdgeAPI的REST接口
|
||||
apiConfig, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "无法获取API配置: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if len(apiConfig.RPCEndpoints) == 0 {
|
||||
this.writeJSON(http.StatusInternalServerError, "未配置RPC端点", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 使用第一个RPC端点,转换为REST端点
|
||||
rpcEndpoint := apiConfig.RPCEndpoints[0]
|
||||
u, err := url.Parse(rpcEndpoint)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "无效的RPC端点: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// REST接口路径:/ServerService/UpdateServerWebConfigForUser
|
||||
restURL := u.Scheme + "://" + u.Host + "/ServerService/UpdateServerWebConfigForUser"
|
||||
|
||||
// 构建请求体
|
||||
requestBody := map[string]interface{}{
|
||||
"serverId": requestParams.ServerId,
|
||||
"domains": requestParams.Domains,
|
||||
"autoCreateCert": true,
|
||||
}
|
||||
requestJSON, err := json.Marshal(requestBody)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "构建请求失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建HTTP请求
|
||||
req, err := http.NewRequest("POST", restURL, bytes.NewReader(requestJSON))
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "创建请求失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Edge-Access-Token", accessToken)
|
||||
|
||||
// 发送请求
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "调用API失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "读取响应失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
var apiResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
}
|
||||
err = json.Unmarshal(respBody, &apiResp)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "解析响应失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if apiResp.Code != 200 {
|
||||
this.writeJSON(apiResp.Code, apiResp.Message, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 格式化返回数据
|
||||
result := make(map[string]string)
|
||||
if resultData, ok := apiResp.Data["result"].(map[string]interface{}); ok {
|
||||
if domains, ok := resultData["domains"].(map[string]interface{}); ok {
|
||||
for domain, statusObj := range domains {
|
||||
if statusMap, ok := statusObj.(map[string]interface{}); ok {
|
||||
if status, ok := statusMap["status"].(string); ok {
|
||||
if status == "success" {
|
||||
result[domain] = "success"
|
||||
} else {
|
||||
reason := ""
|
||||
if r, ok := statusMap["reason"].(string); ok {
|
||||
reason = r
|
||||
}
|
||||
result[domain] = "fail: " + reason
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.writeJSON(http.StatusOK, "ok", map[string]interface{}{
|
||||
"result": result,
|
||||
})
|
||||
}
|
||||
|
||||
// writeJSON 写入JSON响应
|
||||
func (this *DomainsAddAction) writeJSON(code int, message string, data interface{}) {
|
||||
this.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
this.ResponseWriter.WriteHeader(code)
|
||||
|
||||
response := map[string]interface{}{
|
||||
"code": code,
|
||||
"message": message,
|
||||
"data": data,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
this.ResponseWriter.WriteHeader(http.StatusInternalServerError)
|
||||
this.ResponseWriter.Write([]byte(`{"code":500,"message":"marshal json failed","data":null}`))
|
||||
return
|
||||
}
|
||||
|
||||
this.ResponseWriter.Write(jsonData)
|
||||
}
|
||||
|
||||
// getAccessToken 获取Access Token
|
||||
func (this *DomainsAddAction) getAccessToken(userId int64) (string, error) {
|
||||
// 获取用户的AccessKey
|
||||
accessKeysResp, err := this.RPC().UserAccessKeyRPC().FindAllEnabledUserAccessKeys(this.UserContext(), &pb.FindAllEnabledUserAccessKeysRequest{
|
||||
UserId: userId,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(accessKeysResp.UserAccessKeys) == 0 {
|
||||
return "", errors.New("用户未配置AccessKey,请先在设置中创建AccessKey")
|
||||
}
|
||||
|
||||
// 使用第一个AccessKey
|
||||
accessKey := accessKeysResp.UserAccessKeys[0]
|
||||
|
||||
// 获取EdgeAPI地址
|
||||
apiConfig, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(apiConfig.RPCEndpoints) == 0 {
|
||||
return "", errors.New("未配置RPC端点")
|
||||
}
|
||||
|
||||
// 调用GetAPIAccessToken接口
|
||||
rpcEndpoint := apiConfig.RPCEndpoints[0]
|
||||
u, err := url.Parse(rpcEndpoint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
restURL := u.Scheme + "://" + u.Host + "/APIAccessTokenService/getAPIAccessToken"
|
||||
|
||||
// 构建请求体
|
||||
requestBody := map[string]interface{}{
|
||||
"type": "user",
|
||||
"accessKeyId": accessKey.UniqueId,
|
||||
"accessKey": accessKey.Secret,
|
||||
}
|
||||
requestJSON, err := json.Marshal(requestBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 创建HTTP请求
|
||||
req, err := http.NewRequest("POST", restURL, bytes.NewReader(requestJSON))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// 发送请求
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
tokenRespBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
var tokenResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresAt int64 `json:"expiresAt"`
|
||||
} `json:"data"`
|
||||
}
|
||||
err = json.Unmarshal(tokenRespBody, &tokenResp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if tokenResp.Code != 200 {
|
||||
return "", errors.New(tokenResp.Message)
|
||||
}
|
||||
|
||||
return tokenResp.Data.Token, nil
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DomainsDeleteAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *DomainsDeleteAction) RunPost(params struct{}) {
|
||||
// 获取当前登录用户ID
|
||||
userId := this.UserId()
|
||||
if userId <= 0 {
|
||||
this.writeJSON(http.StatusUnauthorized, "请先登录", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 手动读取JSON请求体
|
||||
body, err := io.ReadAll(this.Request.Body)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusBadRequest, "读取请求体失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析JSON参数
|
||||
var requestParams struct {
|
||||
ServerId int64 `json:"serverId"`
|
||||
Domains []string `json:"domains"`
|
||||
}
|
||||
err = json.Unmarshal(body, &requestParams)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusBadRequest, "解析请求参数失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 参数验证
|
||||
if requestParams.ServerId <= 0 {
|
||||
this.writeJSON(http.StatusBadRequest, "serverId不能为空", nil)
|
||||
return
|
||||
}
|
||||
if len(requestParams.Domains) == 0 {
|
||||
this.writeJSON(http.StatusBadRequest, "domains不能为空", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 注意:权限验证由 ResetServerWebConfigForUser 内部完成,这里不需要单独调用 CheckUserServer
|
||||
// 获取Access Token
|
||||
accessToken, err := this.getAccessToken(userId)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "获取访问令牌失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 通过HTTP调用EdgeAPI的REST接口
|
||||
apiConfig, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "无法获取API配置: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if len(apiConfig.RPCEndpoints) == 0 {
|
||||
this.writeJSON(http.StatusInternalServerError, "未配置RPC端点", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 使用第一个RPC端点,转换为REST端点
|
||||
rpcEndpoint := apiConfig.RPCEndpoints[0]
|
||||
u, err := url.Parse(rpcEndpoint)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "无效的RPC端点: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// REST接口路径:/ServerService/ResetServerWebConfigForUser
|
||||
restURL := u.Scheme + "://" + u.Host + "/ServerService/ResetServerWebConfigForUser"
|
||||
|
||||
// 构建请求体
|
||||
requestBody := map[string]interface{}{
|
||||
"serverId": requestParams.ServerId,
|
||||
"domains": requestParams.Domains,
|
||||
}
|
||||
requestJSON, err := json.Marshal(requestBody)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "构建请求失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建HTTP请求
|
||||
req, err := http.NewRequest("POST", restURL, bytes.NewReader(requestJSON))
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "创建请求失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Edge-Access-Token", accessToken)
|
||||
|
||||
// 发送请求
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "调用API失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "读取响应失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
var apiResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
}
|
||||
err = json.Unmarshal(respBody, &apiResp)
|
||||
if err != nil {
|
||||
this.writeJSON(http.StatusInternalServerError, "解析响应失败: "+err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if apiResp.Code != 200 {
|
||||
this.writeJSON(apiResp.Code, apiResp.Message, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 格式化返回数据
|
||||
result := make(map[string]string)
|
||||
if resultData, ok := apiResp.Data["result"].(map[string]interface{}); ok {
|
||||
if domains, ok := resultData["domains"].(map[string]interface{}); ok {
|
||||
for domain, statusObj := range domains {
|
||||
if statusMap, ok := statusObj.(map[string]interface{}); ok {
|
||||
if status, ok := statusMap["status"].(string); ok {
|
||||
if status == "success" {
|
||||
result[domain] = "success"
|
||||
} else {
|
||||
reason := ""
|
||||
if r, ok := statusMap["reason"].(string); ok {
|
||||
reason = r
|
||||
}
|
||||
result[domain] = "fail: " + reason
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.writeJSON(http.StatusOK, "ok", map[string]interface{}{
|
||||
"result": result,
|
||||
})
|
||||
}
|
||||
|
||||
// writeJSON 写入JSON响应
|
||||
func (this *DomainsDeleteAction) writeJSON(code int, message string, data interface{}) {
|
||||
this.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
this.ResponseWriter.WriteHeader(code)
|
||||
|
||||
response := map[string]interface{}{
|
||||
"code": code,
|
||||
"message": message,
|
||||
"data": data,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
this.ResponseWriter.WriteHeader(http.StatusInternalServerError)
|
||||
this.ResponseWriter.Write([]byte(`{"code":500,"message":"marshal json failed","data":null}`))
|
||||
return
|
||||
}
|
||||
|
||||
this.ResponseWriter.Write(jsonData)
|
||||
}
|
||||
|
||||
// getAccessToken 获取Access Token
|
||||
func (this *DomainsDeleteAction) getAccessToken(userId int64) (string, error) {
|
||||
// 获取用户的AccessKey
|
||||
accessKeysResp, err := this.RPC().UserAccessKeyRPC().FindAllEnabledUserAccessKeys(this.UserContext(), &pb.FindAllEnabledUserAccessKeysRequest{
|
||||
UserId: userId,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(accessKeysResp.UserAccessKeys) == 0 {
|
||||
return "", errors.New("用户未配置AccessKey,请先在设置中创建AccessKey")
|
||||
}
|
||||
|
||||
// 使用第一个AccessKey
|
||||
accessKey := accessKeysResp.UserAccessKeys[0]
|
||||
|
||||
// 获取EdgeAPI地址
|
||||
apiConfig, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(apiConfig.RPCEndpoints) == 0 {
|
||||
return "", errors.New("未配置RPC端点")
|
||||
}
|
||||
|
||||
// 调用GetAPIAccessToken接口
|
||||
rpcEndpoint := apiConfig.RPCEndpoints[0]
|
||||
u, err := url.Parse(rpcEndpoint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
restURL := u.Scheme + "://" + u.Host + "/APIAccessTokenService/getAPIAccessToken"
|
||||
|
||||
// 构建请求体
|
||||
requestBody := map[string]interface{}{
|
||||
"type": "user",
|
||||
"accessKeyId": accessKey.UniqueId,
|
||||
"accessKey": accessKey.Secret,
|
||||
}
|
||||
requestJSON, err := json.Marshal(requestBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 创建HTTP请求
|
||||
req, err := http.NewRequest("POST", restURL, bytes.NewReader(requestJSON))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// 发送请求
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
tokenRespBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
var tokenResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresAt int64 `json:"expiresAt"`
|
||||
} `json:"data"`
|
||||
}
|
||||
err = json.Unmarshal(tokenRespBody, &tokenResp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if tokenResp.Code != 200 {
|
||||
return "", errors.New(tokenResp.Message)
|
||||
}
|
||||
|
||||
return tokenResp.Data.Token, nil
|
||||
}
|
||||
74
EdgeUser/internal/web/actions/default/api/servers/list.go
Normal file
74
EdgeUser/internal/web/actions/default/api/servers/list.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ServersListAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *ServersListAction) RunPost(params struct {
|
||||
Username string `json:"username"`
|
||||
}) {
|
||||
// 获取当前登录用户ID
|
||||
userId := this.UserId()
|
||||
if userId <= 0 {
|
||||
this.writeJSON(http.StatusUnauthorized, "请先登录", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 如果提供了username,验证是否为当前用户(可选验证)
|
||||
if len(params.Username) > 0 {
|
||||
// 可以通过RPC获取用户信息并验证,这里简化处理
|
||||
// 实际使用时,如果提供了username,应该验证是否为当前用户
|
||||
}
|
||||
|
||||
// 调用RPC获取用户的所有网站
|
||||
resp, err := this.RPC().ServerRPC().FindAllUserServers(this.UserContext(), &pb.FindAllUserServersRequest{
|
||||
UserId: userId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 格式化返回数据
|
||||
var servers []map[string]interface{}
|
||||
for _, server := range resp.Servers {
|
||||
servers = append(servers, map[string]interface{}{
|
||||
"id": server.Id,
|
||||
"isOn": server.IsOn,
|
||||
"name": server.Name,
|
||||
"firstServerName": server.FirstServerName,
|
||||
})
|
||||
}
|
||||
|
||||
this.writeJSON(http.StatusOK, "ok", map[string]interface{}{
|
||||
"servers": servers,
|
||||
})
|
||||
}
|
||||
|
||||
// writeJSON 写入JSON响应
|
||||
func (this *ServersListAction) writeJSON(code int, message string, data interface{}) {
|
||||
this.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
this.ResponseWriter.WriteHeader(code)
|
||||
|
||||
response := map[string]interface{}{
|
||||
"code": code,
|
||||
"message": message,
|
||||
"data": data,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
this.ResponseWriter.WriteHeader(http.StatusInternalServerError)
|
||||
this.ResponseWriter.Write([]byte(`{"code":500,"message":"marshal json failed","data":null}`))
|
||||
return
|
||||
}
|
||||
|
||||
this.ResponseWriter.Write(jsonData)
|
||||
}
|
||||
12
EdgeUser/internal/web/actions/default/csrf/init.go
Normal file
12
EdgeUser/internal/web/actions/default/csrf/init.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package csrf
|
||||
|
||||
import "github.com/iwind/TeaGo"
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Prefix("/csrf").
|
||||
Get("/token", new(TokenAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
39
EdgeUser/internal/web/actions/default/csrf/token.go
Normal file
39
EdgeUser/internal/web/actions/default/csrf/token.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package csrf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/csrf"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var lastTimestamp = int64(0)
|
||||
var locker sync.Mutex
|
||||
|
||||
type TokenAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *TokenAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *TokenAction) RunGet(params struct {
|
||||
Auth *helpers.UserShouldAuth
|
||||
}) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
defer func() {
|
||||
lastTimestamp = time.Now().UnixNano()
|
||||
}()
|
||||
|
||||
// 没有登录,则限制请求速度
|
||||
if params.Auth.UserId() <= 0 && lastTimestamp > 0 && time.Now().UnixNano()-lastTimestamp <= 300_000_000 {
|
||||
this.Fail("请求速度过快,请稍后刷新后重试")
|
||||
}
|
||||
|
||||
this.Data["token"] = csrf.Generate()
|
||||
this.Success()
|
||||
}
|
||||
179
EdgeUser/internal/web/actions/default/dashboard/index.go
Normal file
179
EdgeUser/internal/web/actions/default/dashboard/index.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils/numberutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
// 是否开启了CDN
|
||||
registerConfig, err := configloaders.LoadRegisterConfig()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if registerConfig != nil && !registerConfig.CDNIsOn {
|
||||
if registerConfig.NSIsOn {
|
||||
this.RedirectURL("/dashboard/ns")
|
||||
return
|
||||
}
|
||||
|
||||
// 显示空白
|
||||
this.View("@blank")
|
||||
this.Show()
|
||||
return
|
||||
}
|
||||
|
||||
// 是否需要审核和实名认证
|
||||
this.Data["isVerified"] = this.Context.GetBool("isVerified")
|
||||
this.Data["isIdentified"] = this.Context.GetBool("isIdentified")
|
||||
|
||||
if !configloaders.RequireVerification() {
|
||||
this.Data["isVerified"] = true
|
||||
}
|
||||
if !configloaders.RequireIdentity() {
|
||||
this.Data["isIdentified"] = true
|
||||
}
|
||||
|
||||
// 查看UI配置
|
||||
uiConfig, _ := configloaders.LoadUIConfig()
|
||||
this.Data["uiConfig"] = maps.Map{
|
||||
"showTrafficCharts": true,
|
||||
"showBandwidthCharts": true,
|
||||
"bandwidthUnit": systemconfigs.BandwidthUnitBit,
|
||||
}
|
||||
if uiConfig != nil {
|
||||
this.Data["uiConfig"] = maps.Map{
|
||||
"showTrafficCharts": uiConfig.ShowTrafficCharts,
|
||||
"showBandwidthCharts": uiConfig.ShowBandwidthCharts,
|
||||
"bandwidthUnit": systemconfigs.BandwidthUnitBit, // 强制使用比特单位
|
||||
}
|
||||
}
|
||||
|
||||
// 是否需要激活邮箱
|
||||
var userResp *pb.FindEnabledUserResponse
|
||||
this.Data["emailVerificationMessage"] = ""
|
||||
if registerConfig != nil && registerConfig.EmailVerification.IsOn {
|
||||
// 尚未激活
|
||||
verificationResp, err := this.RPC().UserEmailVerificationRPC().FindLatestUserEmailVerification(this.UserContext(), &pb.FindLatestUserEmailVerificationRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if verificationResp.UserEmailVerification != nil {
|
||||
this.Data["emailVerificationMessage"] = "电子邮箱等待激活,请及时激活。"
|
||||
} else if registerConfig.EmailVerification.ShowNotice {
|
||||
// 尚未绑定
|
||||
var userErr error
|
||||
userResp, userErr = this.RPC().UserRPC().FindEnabledUser(this.UserContext(), &pb.FindEnabledUserRequest{UserId: this.UserId()})
|
||||
if userErr != nil {
|
||||
this.ErrorPage(userErr)
|
||||
return
|
||||
}
|
||||
if userResp.User != nil && len(userResp.User.VerifiedEmail) == 0 {
|
||||
this.Data["emailVerificationMessage"] = "尚未绑定电子邮箱,请点此绑定。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 是否需要验证手机号
|
||||
this.Data["mobileVerificationMessage"] = ""
|
||||
if registerConfig != nil && registerConfig.MobileVerification.IsOn && registerConfig.MobileVerification.ShowNotice {
|
||||
// 尚未绑定
|
||||
if userResp == nil {
|
||||
var userErr error
|
||||
userResp, userErr = this.RPC().UserRPC().FindEnabledUser(this.UserContext(), &pb.FindEnabledUserRequest{UserId: this.UserId()})
|
||||
if userErr != nil {
|
||||
this.ErrorPage(userErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
if userResp.User != nil && len(userResp.User.VerifiedMobile) == 0 {
|
||||
this.Data["mobileVerificationMessage"] = "尚未绑定手机号码,请点此绑定。"
|
||||
}
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunPost(params struct{}) {
|
||||
// 查看UI配置
|
||||
uiConfig, _ := configloaders.LoadUIConfig()
|
||||
this.Data["uiConfig"] = maps.Map{
|
||||
"showTrafficCharts": true,
|
||||
"showBandwidthCharts": true,
|
||||
"bandwidthUnit": systemconfigs.BandwidthUnitBit,
|
||||
}
|
||||
if uiConfig != nil {
|
||||
this.Data["uiConfig"] = maps.Map{
|
||||
"showTrafficCharts": uiConfig.ShowTrafficCharts,
|
||||
"showBandwidthCharts": uiConfig.ShowBandwidthCharts,
|
||||
"bandwidthUnit": systemconfigs.BandwidthUnitBit, // 强制使用比特单位
|
||||
}
|
||||
}
|
||||
|
||||
dashboardResp, err := this.RPC().UserRPC().ComposeUserDashboard(this.UserContext(), &pb.ComposeUserDashboardRequest{UserId: this.UserId()})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var monthlyPeekBandwidthBytes = dashboardResp.MonthlyPeekBandwidthBytes
|
||||
var dailyPeekBandwidthBytes = dashboardResp.DailyPeekBandwidthBytes
|
||||
monthlyPeekBandwidthBytes *= 8
|
||||
dailyPeekBandwidthBytes *= 8
|
||||
this.Data["dashboard"] = maps.Map{
|
||||
"countServers": dashboardResp.CountServers,
|
||||
"monthlyTrafficBytes": numberutils.FormatBytes(dashboardResp.MonthlyTrafficBytes),
|
||||
"monthlyPeekBandwidthBytes": numberutils.FormatBits(monthlyPeekBandwidthBytes),
|
||||
"dailyTrafficBytes": numberutils.FormatBytes(dashboardResp.DailyTrafficBytes),
|
||||
"dailyPeekBandwidthBytes": numberutils.FormatBits(dailyPeekBandwidthBytes),
|
||||
}
|
||||
|
||||
// 每日流量统计
|
||||
{
|
||||
var statMaps = []maps.Map{}
|
||||
for _, stat := range dashboardResp.DailyTrafficStats {
|
||||
var infoMap = maps.Map{
|
||||
"bytes": stat.Bytes,
|
||||
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
|
||||
"countRequests": stat.CountRequests,
|
||||
}
|
||||
if uiConfig.ShowCacheInfoInTrafficCharts {
|
||||
infoMap["cachedBytes"] = stat.CachedBytes
|
||||
infoMap["countCachedRequests"] = stat.CountCachedRequests
|
||||
}
|
||||
statMaps = append(statMaps, infoMap)
|
||||
}
|
||||
this.Data["dailyTrafficStats"] = statMaps
|
||||
}
|
||||
|
||||
// 每日峰值带宽统计
|
||||
{
|
||||
var statMaps = []maps.Map{}
|
||||
for _, stat := range dashboardResp.DailyPeekBandwidthStats {
|
||||
statMaps = append(statMaps, maps.Map{
|
||||
"bytes": stat.Bytes,
|
||||
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
|
||||
})
|
||||
}
|
||||
this.Data["dailyPeekBandwidthStats"] = statMaps
|
||||
}
|
||||
|
||||
this.Data["bandwidthPercentile"] = dashboardResp.BandwidthPercentile
|
||||
this.Data["bandwidthPercentileBits"] = dashboardResp.BandwidthPercentileBits
|
||||
|
||||
this.Success()
|
||||
}
|
||||
18
EdgeUser/internal/web/actions/default/dashboard/init.go
Normal file
18
EdgeUser/internal/web/actions/default/dashboard/init.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth("")).
|
||||
Data("teaMenu", "dashboard").
|
||||
Prefix("/dashboard").
|
||||
GetPost("", new(IndexAction)).
|
||||
Get("/ns", new(NsAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
67
EdgeUser/internal/web/actions/default/dashboard/ns.go
Normal file
67
EdgeUser/internal/web/actions/default/dashboard/ns.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type NsAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *NsAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *NsAction) RunGet(params struct{}) {
|
||||
// 是否开启了CDN
|
||||
registerConfig, err := configloaders.LoadRegisterConfig()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if registerConfig != nil && !registerConfig.NSIsOn {
|
||||
// 显示空白
|
||||
this.View("@blank")
|
||||
this.Show()
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := this.RPC().NSRPC().ComposeNSUserBoard(this.UserContext(), &pb.ComposeNSUserBoardRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var planName = ""
|
||||
if resp.NsUserPlan != nil && resp.NsUserPlan.NsPlan != nil {
|
||||
planName = resp.NsUserPlan.NsPlan.Name
|
||||
}
|
||||
|
||||
this.Data["board"] = maps.Map{
|
||||
"countDomains": resp.CountNSDomains,
|
||||
"countRecords": resp.CountNSRecords,
|
||||
"countRoutes": resp.CountNSRoutes,
|
||||
"planName": planName,
|
||||
}
|
||||
|
||||
// 域名排行
|
||||
{
|
||||
var statMaps = []maps.Map{}
|
||||
for _, stat := range resp.TopNSDomainStats {
|
||||
statMaps = append(statMaps, maps.Map{
|
||||
"domainId": stat.NsDomainId,
|
||||
"domainName": stat.NsDomainName,
|
||||
"countRequests": stat.CountRequests,
|
||||
"bytes": stat.Bytes,
|
||||
})
|
||||
}
|
||||
this.Data["topDomainStats"] = statMaps
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
39
EdgeUser/internal/web/actions/default/dns/domainOptions.go
Normal file
39
EdgeUser/internal/web/actions/default/dns/domainOptions.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// DomainOptionsAction 域名列表选项
|
||||
type DomainOptionsAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *DomainOptionsAction) RunPost(params struct {
|
||||
ProviderId int64
|
||||
}) {
|
||||
domainsResp, err := this.RPC().DNSDomainRPC().FindAllBasicDNSDomainsWithDNSProviderId(this.UserContext(), &pb.FindAllBasicDNSDomainsWithDNSProviderIdRequest{
|
||||
DnsProviderId: params.ProviderId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
domainMaps := []maps.Map{}
|
||||
for _, domain := range domainsResp.DnsDomains {
|
||||
// 未开启或者已删除的先跳过
|
||||
if !domain.IsOn || domain.IsDeleted {
|
||||
continue
|
||||
}
|
||||
|
||||
domainMaps = append(domainMaps, maps.Map{
|
||||
"id": domain.Id,
|
||||
"name": domain.Name,
|
||||
})
|
||||
}
|
||||
this.Data["domains"] = domainMaps
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package domains
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type ClustersPopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *ClustersPopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *ClustersPopupAction) RunGet(params struct {
|
||||
DomainId int64
|
||||
}) {
|
||||
// 域名信息
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindBasicDNSDomain(this.UserContext(), &pb.FindBasicDNSDomainRequest{
|
||||
DnsDomainId: params.DomainId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
domain := domainResp.DnsDomain
|
||||
if domain == nil {
|
||||
this.NotFound("dnsDomain", params.DomainId)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["domain"] = domain.Name
|
||||
|
||||
// 集群
|
||||
clustersResp, err := this.RPC().NodeClusterRPC().FindAllEnabledNodeClustersWithDNSDomainId(this.UserContext(), &pb.FindAllEnabledNodeClustersWithDNSDomainIdRequest{DnsDomainId: params.DomainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
clusterMaps := []maps.Map{}
|
||||
for _, cluster := range clustersResp.NodeClusters {
|
||||
isOk := false
|
||||
if len(cluster.Name) > 0 {
|
||||
for _, recordType := range []string{"A", "AAAA"} {
|
||||
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.UserContext(), &pb.ExistDNSDomainRecordRequest{
|
||||
DnsDomainId: params.DomainId,
|
||||
Name: cluster.DnsName,
|
||||
Type: recordType,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if checkResp.IsOk {
|
||||
isOk = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clusterMaps = append(clusterMaps, maps.Map{
|
||||
"id": cluster.Id,
|
||||
"name": cluster.Name,
|
||||
"dnsName": cluster.DnsName,
|
||||
"isOk": isOk,
|
||||
})
|
||||
}
|
||||
this.Data["clusters"] = clusterMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package domains
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/dns/domains/domainutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CreatePopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunGet(params struct {
|
||||
ProviderId int64
|
||||
}) {
|
||||
this.Data["providerId"] = params.ProviderId
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunPost(params struct {
|
||||
ProviderId int64
|
||||
Name string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
// TODO 检查ProviderId
|
||||
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入域名")
|
||||
|
||||
// 校验域名
|
||||
domain := strings.ToLower(params.Name)
|
||||
domain = strings.Replace(domain, " ", "", -1)
|
||||
if !domainutils.ValidateDomainFormat(domain) {
|
||||
this.Fail("域名格式不正确,请修改后重新提交")
|
||||
}
|
||||
|
||||
createResp, err := this.RPC().DNSDomainRPC().CreateDNSDomain(this.UserContext(), &pb.CreateDNSDomainRequest{
|
||||
DnsProviderId: params.ProviderId,
|
||||
Name: domain,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer this.CreateLogInfo(codes.DNS_LogCreateDomain, createResp.DnsDomainId)
|
||||
|
||||
this.Success()
|
||||
}
|
||||
38
EdgeUser/internal/web/actions/default/dns/domains/delete.go
Normal file
38
EdgeUser/internal/web/actions/default/dns/domains/delete.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package domains
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils/numberutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
type DeleteAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *DeleteAction) RunPost(params struct {
|
||||
DomainId int64
|
||||
}) {
|
||||
// 记录日志
|
||||
defer this.CreateLogInfo(codes.DNS_LogDeleteDomain, params.DomainId)
|
||||
|
||||
// 检查是否正在使用
|
||||
countResp, err := this.RPC().NodeClusterRPC().CountAllEnabledNodeClustersWithDNSDomainId(this.UserContext(), &pb.CountAllEnabledNodeClustersWithDNSDomainIdRequest{DnsDomainId: params.DomainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if countResp.Count > 0 {
|
||||
this.Fail("当前域名正在被" + numberutils.FormatInt64(countResp.Count) + "个集群所使用,所以不能删除。请修改后再操作。")
|
||||
}
|
||||
|
||||
// 执行删除
|
||||
_, err = this.RPC().DNSDomainRPC().DeleteDNSDomain(this.UserContext(), &pb.DeleteDNSDomainRequest{DnsDomainId: params.DomainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package domainutils
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ValidateDomainFormat 校验域名格式
|
||||
func ValidateDomainFormat(domain string) bool {
|
||||
pieces := strings.Split(domain, ".")
|
||||
for _, piece := range pieces {
|
||||
if piece == "-" ||
|
||||
strings.HasPrefix(piece, "-") ||
|
||||
strings.HasSuffix(piece, "-") ||
|
||||
len(piece) > 63 ||
|
||||
// 我们允许中文、大写字母、下划线,防止有些特殊场景下需要
|
||||
!regexp.MustCompile(`^[\p{Han}_a-zA-Z0-9-]+$`).MatchString(piece) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 最后一段不能是全数字
|
||||
if regexp.MustCompile(`^(\d+)$`).MatchString(pieces[len(pieces)-1]) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ConvertRoutesToMaps 转换线路列表
|
||||
func ConvertRoutesToMaps(info *pb.NodeDNSInfo) []maps.Map {
|
||||
if info == nil {
|
||||
return []maps.Map{}
|
||||
}
|
||||
result := []maps.Map{}
|
||||
for _, route := range info.Routes {
|
||||
result = append(result, maps.Map{
|
||||
"name": route.Name,
|
||||
"code": route.Code,
|
||||
"domainId": info.DnsDomainId,
|
||||
"domainName": info.DnsDomainName,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// FilterRoutes 筛选线路
|
||||
func FilterRoutes(routes []*pb.DNSRoute, allRoutes []*pb.DNSRoute) []*pb.DNSRoute {
|
||||
routeCodes := []string{}
|
||||
for _, route := range allRoutes {
|
||||
routeCodes = append(routeCodes, route.Code)
|
||||
}
|
||||
result := []*pb.DNSRoute{}
|
||||
for _, route := range routes {
|
||||
if lists.ContainsString(routeCodes, route.Code) {
|
||||
result = append(result, route)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ValidateRecordName 校验记录名
|
||||
func ValidateRecordName(name string) bool {
|
||||
if name == "*" || name == "@" || len(name) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
pieces := strings.Split(name, ".")
|
||||
for index, piece := range pieces {
|
||||
if index == 0 && piece == "*" {
|
||||
continue
|
||||
}
|
||||
if piece == "-" ||
|
||||
strings.HasPrefix(piece, "-") ||
|
||||
strings.HasSuffix(piece, "-") ||
|
||||
//strings.Contains(piece, "--") ||
|
||||
len(piece) > 63 ||
|
||||
// 我们允许中文、大写字母、下划线,防止有些特殊场景下需要
|
||||
!regexp.MustCompile(`^[\p{Han}_a-zA-Z0-9-]+$`).MatchString(piece) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ValidateRecordValue 校验记录值
|
||||
func ValidateRecordValue(recordType dnsconfigs.RecordType, value string) (message string, ok bool) {
|
||||
switch recordType {
|
||||
case dnsconfigs.RecordTypeA:
|
||||
if !regexp.MustCompile(`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$`).MatchString(value) {
|
||||
message = "请输入正确格式的IP"
|
||||
return
|
||||
}
|
||||
if net.ParseIP(value) == nil {
|
||||
message = "请输入正确格式的IP"
|
||||
return
|
||||
}
|
||||
case dnsconfigs.RecordTypeCNAME:
|
||||
value = strings.TrimSuffix(value, ".")
|
||||
if !strings.Contains(value, ".") || !ValidateDomainFormat(value) {
|
||||
message = "请输入正确的域名"
|
||||
return
|
||||
}
|
||||
case dnsconfigs.RecordTypeAAAA:
|
||||
if !strings.Contains(value, ":") {
|
||||
message = "请输入正确格式的IPv6地址"
|
||||
return
|
||||
}
|
||||
if net.ParseIP(value) == nil {
|
||||
message = "请输入正确格式的IPv6地址"
|
||||
return
|
||||
}
|
||||
case dnsconfigs.RecordTypeNS:
|
||||
value = strings.TrimSuffix(value, ".")
|
||||
if !strings.Contains(value, ".") || !ValidateDomainFormat(value) {
|
||||
message = "请输入正确的DNS服务器域名"
|
||||
return
|
||||
}
|
||||
case dnsconfigs.RecordTypeMX:
|
||||
value = strings.TrimSuffix(value, ".")
|
||||
if !strings.Contains(value, ".") || !ValidateDomainFormat(value) {
|
||||
message = "请输入正确的邮件服务器域名"
|
||||
return
|
||||
}
|
||||
case dnsconfigs.RecordTypeSRV:
|
||||
if len(value) == 0 {
|
||||
message = "请输入主机名"
|
||||
return
|
||||
}
|
||||
case dnsconfigs.RecordTypeTXT:
|
||||
if len(value) > 512 {
|
||||
message = "文本长度不能超出512字节"
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(value) > 512 {
|
||||
message = "记录值长度不能超出512字节"
|
||||
return
|
||||
}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package domainutils
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateRecordValue(t *testing.T) {
|
||||
a := assert.NewAssertion(t)
|
||||
|
||||
// A
|
||||
{
|
||||
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeA, "1.2")
|
||||
a.IsFalse(ok)
|
||||
}
|
||||
{
|
||||
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeA, "1.2.3.400")
|
||||
a.IsFalse(ok)
|
||||
}
|
||||
{
|
||||
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeA, "1.2.3.4")
|
||||
a.IsTrue(ok)
|
||||
}
|
||||
|
||||
// CNAME
|
||||
{
|
||||
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeCNAME, "example.com")
|
||||
a.IsTrue(ok)
|
||||
}
|
||||
{
|
||||
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeCNAME, "example.com.")
|
||||
a.IsTrue(ok)
|
||||
}
|
||||
{
|
||||
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeCNAME, "hello, world")
|
||||
a.IsFalse(ok)
|
||||
}
|
||||
|
||||
// AAAA
|
||||
{
|
||||
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeAAAA, "1.2.3.4")
|
||||
a.IsFalse(ok)
|
||||
}
|
||||
{
|
||||
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeAAAA, "2001:0db8:85a3:0000:0000:8a2e:0370:7334")
|
||||
a.IsTrue(ok)
|
||||
}
|
||||
|
||||
// NS
|
||||
{
|
||||
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeNS, "1.2.3.4")
|
||||
a.IsFalse(ok)
|
||||
}
|
||||
{
|
||||
_, ok := ValidateRecordValue(dnsconfigs.RecordTypeNS, "example.com")
|
||||
a.IsTrue(ok)
|
||||
}
|
||||
}
|
||||
146
EdgeUser/internal/web/actions/default/dns/domains/nodesPopup.go
Normal file
146
EdgeUser/internal/web/actions/default/dns/domains/nodesPopup.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package domains
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type NodesPopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *NodesPopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *NodesPopupAction) RunGet(params struct {
|
||||
DomainId int64
|
||||
}) {
|
||||
this.Data["domainId"] = params.DomainId
|
||||
|
||||
// 域名信息
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindBasicDNSDomain(this.UserContext(), &pb.FindBasicDNSDomainRequest{
|
||||
DnsDomainId: params.DomainId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var domain = domainResp.DnsDomain
|
||||
if domain == nil {
|
||||
this.NotFound("dnsDomain", params.DomainId)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["domain"] = domain.Name
|
||||
|
||||
// 集群
|
||||
var clusterMaps = []maps.Map{}
|
||||
clustersResp, err := this.RPC().NodeClusterRPC().FindAllEnabledNodeClustersWithDNSDomainId(this.UserContext(), &pb.FindAllEnabledNodeClustersWithDNSDomainIdRequest{DnsDomainId: params.DomainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, cluster := range clustersResp.NodeClusters {
|
||||
// 默认值
|
||||
var defaultRoute = cluster.DnsDefaultRoute
|
||||
|
||||
// 节点DNS解析记录
|
||||
nodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesDNSWithNodeClusterId(this.UserContext(), &pb.FindAllEnabledNodesDNSWithNodeClusterIdRequest{
|
||||
NodeClusterId: cluster.Id,
|
||||
IsInstalled: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var nodeMaps = []maps.Map{}
|
||||
for _, node := range nodesResp.Nodes {
|
||||
if len(node.Routes) > 0 {
|
||||
for _, route := range node.Routes {
|
||||
// 检查是否有域名解析记录
|
||||
var isResolved = false
|
||||
if len(route.Name) > 0 && len(node.IpAddr) > 0 && len(cluster.DnsName) > 0 {
|
||||
var recordType = "A"
|
||||
if iputils.IsIPv6(node.IpAddr) {
|
||||
recordType = "AAAA"
|
||||
}
|
||||
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.UserContext(), &pb.ExistDNSDomainRecordRequest{
|
||||
DnsDomainId: params.DomainId,
|
||||
Name: cluster.DnsName,
|
||||
Type: recordType,
|
||||
Route: route.Code,
|
||||
Value: node.IpAddr,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
isResolved = checkResp.IsOk
|
||||
}
|
||||
|
||||
nodeMaps = append(nodeMaps, maps.Map{
|
||||
"id": node.Id,
|
||||
"name": node.Name,
|
||||
"ipAddr": node.IpAddr,
|
||||
"route": maps.Map{
|
||||
"name": route.Name,
|
||||
"code": route.Code,
|
||||
},
|
||||
"clusterId": node.NodeClusterId,
|
||||
"isOk": isResolved,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 默认线路
|
||||
var isResolved = false
|
||||
if len(defaultRoute) > 0 {
|
||||
var recordType = "A"
|
||||
if iputils.IsIPv6(node.IpAddr) {
|
||||
recordType = "AAAA"
|
||||
}
|
||||
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.UserContext(), &pb.ExistDNSDomainRecordRequest{
|
||||
DnsDomainId: cluster.DnsDomainId,
|
||||
Name: cluster.DnsName,
|
||||
Type: recordType,
|
||||
Route: defaultRoute,
|
||||
Value: node.IpAddr,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
isResolved = checkResp.IsOk
|
||||
}
|
||||
nodeMaps = append(nodeMaps, maps.Map{
|
||||
"id": node.Id,
|
||||
"name": node.Name,
|
||||
"ipAddr": node.IpAddr,
|
||||
"route": maps.Map{
|
||||
"name": "",
|
||||
"code": "",
|
||||
},
|
||||
"clusterId": node.NodeClusterId,
|
||||
"isOk": isResolved,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(nodeMaps) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
clusterMaps = append(clusterMaps, maps.Map{
|
||||
"id": cluster.Id,
|
||||
"name": cluster.Name,
|
||||
"dnsName": cluster.DnsName,
|
||||
"nodes": nodeMaps,
|
||||
})
|
||||
}
|
||||
this.Data["clusters"] = clusterMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
27
EdgeUser/internal/web/actions/default/dns/domains/recover.go
Normal file
27
EdgeUser/internal/web/actions/default/dns/domains/recover.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package domains
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
type RecoverAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *RecoverAction) RunPost(params struct {
|
||||
DomainId int64
|
||||
}) {
|
||||
// 记录日志
|
||||
defer this.CreateLogInfo(codes.DNS_LogRecoverDomain, params.DomainId)
|
||||
|
||||
// 执行恢复
|
||||
_, err := this.RPC().DNSDomainRPC().RecoverDNSDomain(this.UserContext(), &pb.RecoverDNSDomainRequest{DnsDomainId: params.DomainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package domains
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RoutesPopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *RoutesPopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *RoutesPopupAction) RunGet(params struct {
|
||||
DomainId int64
|
||||
}) {
|
||||
routesResp, err := this.RPC().DNSDomainRPC().FindAllDNSDomainRoutes(this.UserContext(), &pb.FindAllDNSDomainRoutesRequest{DnsDomainId: params.DomainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
routeMaps := []maps.Map{}
|
||||
for _, route := range routesResp.Routes {
|
||||
routeMaps = append(routeMaps, maps.Map{
|
||||
"name": route.Name,
|
||||
"code": route.Code,
|
||||
})
|
||||
}
|
||||
this.Data["routes"] = routeMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
105
EdgeUser/internal/web/actions/default/dns/domains/selectPopup.go
Normal file
105
EdgeUser/internal/web/actions/default/dns/domains/selectPopup.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package domains
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"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 {
|
||||
DomainId int64
|
||||
}) {
|
||||
this.Data["domainId"] = 0
|
||||
this.Data["domainName"] = ""
|
||||
this.Data["providerId"] = 0
|
||||
this.Data["providerType"] = ""
|
||||
|
||||
// 域名信息
|
||||
if params.DomainId > 0 {
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindDNSDomain(this.UserContext(), &pb.FindDNSDomainRequest{DnsDomainId: params.DomainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var domain = domainResp.DnsDomain
|
||||
if domain != nil {
|
||||
this.Data["domainId"] = domain.Id
|
||||
this.Data["domainName"] = domain.Name
|
||||
this.Data["providerId"] = domain.ProviderId
|
||||
|
||||
providerResp, err := this.RPC().DNSProviderRPC().FindEnabledDNSProvider(this.UserContext(), &pb.FindEnabledDNSProviderRequest{DnsProviderId: domain.ProviderId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if providerResp.DnsProvider != nil {
|
||||
this.Data["providerType"] = providerResp.DnsProvider.Type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 所有服务商
|
||||
providerTypesResp, err := this.RPC().DNSProviderRPC().FindAllDNSProviderTypes(this.UserContext(), &pb.FindAllDNSProviderTypesRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var providerTypeMaps = []maps.Map{}
|
||||
for _, providerType := range providerTypesResp.ProviderTypes {
|
||||
providerTypeMaps = append(providerTypeMaps, maps.Map{
|
||||
"name": providerType.Name,
|
||||
"code": providerType.Code,
|
||||
})
|
||||
}
|
||||
this.Data["providerTypes"] = providerTypeMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *SelectPopupAction) RunPost(params struct {
|
||||
DomainId int64
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
this.Data["domainId"] = params.DomainId
|
||||
this.Data["domainName"] = ""
|
||||
this.Data["providerName"] = ""
|
||||
|
||||
if params.DomainId > 0 {
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindDNSDomain(this.UserContext(), &pb.FindDNSDomainRequest{DnsDomainId: params.DomainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if domainResp.DnsDomain != nil {
|
||||
this.Data["domainName"] = domainResp.DnsDomain.Name
|
||||
|
||||
// 服务商名称
|
||||
var providerId = domainResp.DnsDomain.ProviderId
|
||||
if providerId > 0 {
|
||||
providerResp, err := this.RPC().DNSProviderRPC().FindEnabledDNSProvider(this.UserContext(), &pb.FindEnabledDNSProviderRequest{DnsProviderId: providerId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if providerResp.DnsProvider != nil {
|
||||
this.Data["providerName"] = providerResp.DnsProvider.Name
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.Data["domainId"] = 0
|
||||
}
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package domains
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type ServersPopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *ServersPopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *ServersPopupAction) RunGet(params struct {
|
||||
DomainId int64
|
||||
}) {
|
||||
this.Data["domainId"] = params.DomainId
|
||||
|
||||
// 域名信息
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindBasicDNSDomain(this.UserContext(), &pb.FindBasicDNSDomainRequest{
|
||||
DnsDomainId: params.DomainId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var domain = domainResp.DnsDomain
|
||||
if domain == nil {
|
||||
this.NotFound("dnsDomain", params.DomainId)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["domain"] = domain.Name
|
||||
|
||||
// 服务信息
|
||||
var clusterMaps = []maps.Map{}
|
||||
clustersResp, err := this.RPC().NodeClusterRPC().FindAllEnabledNodeClustersWithDNSDomainId(this.UserContext(), &pb.FindAllEnabledNodeClustersWithDNSDomainIdRequest{DnsDomainId: params.DomainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
for _, cluster := range clustersResp.NodeClusters {
|
||||
serversResp, err := this.RPC().ServerRPC().FindAllEnabledServersDNSWithNodeClusterId(this.UserContext(), &pb.FindAllEnabledServersDNSWithNodeClusterIdRequest{NodeClusterId: cluster.Id})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var serverMaps = []maps.Map{}
|
||||
for _, server := range serversResp.Servers {
|
||||
var isOk = false
|
||||
if len(cluster.DnsName) > 0 && len(server.DnsName) > 0 {
|
||||
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.UserContext(), &pb.ExistDNSDomainRecordRequest{
|
||||
DnsDomainId: params.DomainId,
|
||||
Name: server.DnsName,
|
||||
Type: "CNAME",
|
||||
Value: cluster.DnsName + "." + domain.Name,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
isOk = checkResp.IsOk
|
||||
}
|
||||
|
||||
serverMaps = append(serverMaps, maps.Map{
|
||||
"id": server.Id,
|
||||
"name": server.Name,
|
||||
"dnsName": server.DnsName,
|
||||
"isOk": isOk,
|
||||
})
|
||||
}
|
||||
clusterMaps = append(clusterMaps, maps.Map{
|
||||
"id": cluster.Id,
|
||||
"name": cluster.Name,
|
||||
"dnsName": cluster.DnsName,
|
||||
"servers": serverMaps,
|
||||
})
|
||||
}
|
||||
this.Data["clusters"] = clusterMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
33
EdgeUser/internal/web/actions/default/dns/domains/sync.go
Normal file
33
EdgeUser/internal/web/actions/default/dns/domains/sync.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package domains
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
type SyncAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *SyncAction) RunPost(params struct {
|
||||
DomainId int64
|
||||
}) {
|
||||
// 记录日志
|
||||
defer this.CreateLogInfo(codes.DNS_LogSyncDomain, params.DomainId)
|
||||
|
||||
// 执行同步
|
||||
resp, err := this.RPC().DNSDomainRPC().SyncDNSDomainData(this.UserContext(), &pb.SyncDNSDomainDataRequest{DnsDomainId: params.DomainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if resp.IsOk {
|
||||
this.Success()
|
||||
} else {
|
||||
this.Data["shouldFix"] = resp.ShouldFix
|
||||
this.Fail(resp.Error)
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package domains
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/dns/domains/domainutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type UpdatePopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpdatePopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *UpdatePopupAction) RunGet(params struct {
|
||||
DomainId int64
|
||||
}) {
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindDNSDomain(this.UserContext(), &pb.FindDNSDomainRequest{DnsDomainId: params.DomainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
domain := domainResp.DnsDomain
|
||||
if domain == nil {
|
||||
this.NotFound("dnsDomain", params.DomainId)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["domain"] = maps.Map{
|
||||
"id": domain.Id,
|
||||
"name": domain.Name,
|
||||
"isOn": domain.IsOn,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *UpdatePopupAction) RunPost(params struct {
|
||||
DomainId int64
|
||||
Name string
|
||||
IsOn bool
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
// TODO 检查DomainId
|
||||
|
||||
// 记录日志
|
||||
defer this.CreateLogInfo(codes.DNS_LogUpdateDomain, params.DomainId)
|
||||
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入域名")
|
||||
|
||||
// 校验域名
|
||||
domain := strings.ToLower(params.Name)
|
||||
domain = strings.Replace(domain, " ", "", -1)
|
||||
if !domainutils.ValidateDomainFormat(domain) {
|
||||
this.Fail("域名格式不正确,请修改后重新提交")
|
||||
}
|
||||
|
||||
_, err := this.RPC().DNSDomainRPC().UpdateDNSDomain(this.UserContext(), &pb.UpdateDNSDomainRequest{
|
||||
DnsDomainId: params.DomainId,
|
||||
Name: domain,
|
||||
IsOn: params.IsOn,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
17
EdgeUser/internal/web/actions/default/dns/helper.go
Normal file
17
EdgeUser/internal/web/actions/default/dns/helper.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Helper struct {
|
||||
}
|
||||
|
||||
func (this *Helper) BeforeAction(action *actions.ActionObject) {
|
||||
if action.Request.Method != http.MethodGet {
|
||||
return
|
||||
}
|
||||
|
||||
action.Data["teaMenu"] = "dns"
|
||||
}
|
||||
90
EdgeUser/internal/web/actions/default/dns/index.go
Normal file
90
EdgeUser/internal/web/actions/default/dns/index.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("dns", "dns", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
Keyword string
|
||||
}) {
|
||||
//用户端没有集群列表 暂时从定向到dns服务商
|
||||
this.RedirectURL("/dns/providers")
|
||||
return
|
||||
this.Data["keyword"] = params.Keyword
|
||||
|
||||
countResp, err := this.RPC().NodeClusterRPC().CountAllEnabledNodeClusters(this.UserContext(), &pb.CountAllEnabledNodeClustersRequest{
|
||||
Keyword: params.Keyword,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
page := this.NewPage(countResp.Count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
clustersResp, err := this.RPC().NodeClusterRPC().ListEnabledNodeClusters(this.UserContext(), &pb.ListEnabledNodeClustersRequest{
|
||||
Keyword: params.Keyword,
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
clusterMaps := []maps.Map{}
|
||||
for _, cluster := range clustersResp.NodeClusters {
|
||||
domainId := cluster.DnsDomainId
|
||||
domainName := ""
|
||||
providerId := int64(0)
|
||||
providerName := ""
|
||||
providerTypeName := ""
|
||||
|
||||
if cluster.DnsDomainId > 0 {
|
||||
domainResp, err := this.RPC().DNSDomainRPC().FindBasicDNSDomain(this.UserContext(), &pb.FindBasicDNSDomainRequest{DnsDomainId: domainId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
domain := domainResp.DnsDomain
|
||||
if domain == nil {
|
||||
domainId = 0
|
||||
} else {
|
||||
domainName = domain.Name
|
||||
providerResp, err := this.RPC().DNSProviderRPC().FindEnabledDNSProvider(this.UserContext(), &pb.FindEnabledDNSProviderRequest{DnsProviderId: domain.ProviderId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if providerResp.DnsProvider != nil {
|
||||
providerId = providerResp.DnsProvider.Id
|
||||
providerName = providerResp.DnsProvider.Name
|
||||
providerTypeName = providerResp.DnsProvider.TypeName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clusterMaps = append(clusterMaps, maps.Map{
|
||||
"id": cluster.Id,
|
||||
"name": cluster.Name,
|
||||
"dnsName": cluster.DnsName,
|
||||
"domainId": domainId,
|
||||
"domainName": domainName,
|
||||
"providerId": providerId,
|
||||
"providerName": providerName,
|
||||
"providerTypeName": providerTypeName,
|
||||
})
|
||||
}
|
||||
this.Data["clusters"] = clusterMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
51
EdgeUser/internal/web/actions/default/dns/init.go
Normal file
51
EdgeUser/internal/web/actions/default/dns/init.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/dns/domains"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/dns/providers"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth("")).
|
||||
Helper(new(Helper)).
|
||||
Prefix("/dns").
|
||||
Get("", new(IndexAction)).
|
||||
Post("/providerOptions", new(ProviderOptionsAction)).
|
||||
Post("/domainOptions", new(DomainOptionsAction)).
|
||||
|
||||
// 服务商
|
||||
|
||||
Prefix("/dns/providers").
|
||||
Data("teaMenu", "provider").
|
||||
//Data("teaSubMenu", "provider").
|
||||
Get("", new(providers.IndexAction)).
|
||||
GetPost("/createPopup", new(providers.CreatePopupAction)).
|
||||
GetPost("/updatePopup", new(providers.UpdatePopupAction)).
|
||||
Post("/delete", new(providers.DeleteAction)).
|
||||
Get("/provider", new(providers.ProviderAction)).
|
||||
Post("/syncDomains", new(providers.SyncDomainsAction)).
|
||||
EndData().
|
||||
|
||||
// 域名
|
||||
Prefix("/dns/domains").
|
||||
Data("teaSubMenu", "provider").
|
||||
GetPost("/createPopup", new(domains.CreatePopupAction)).
|
||||
GetPost("/updatePopup", new(domains.UpdatePopupAction)).
|
||||
Post("/delete", new(domains.DeleteAction)).
|
||||
Post("/recover", new(domains.RecoverAction)).
|
||||
Post("/sync", new(domains.SyncAction)).
|
||||
Get("/routesPopup", new(domains.RoutesPopupAction)).
|
||||
GetPost("/selectPopup", new(domains.SelectPopupAction)).
|
||||
Get("/clustersPopup", new(domains.ClustersPopupAction)).
|
||||
Get("/nodesPopup", new(domains.NodesPopupAction)).
|
||||
Get("/serversPopup", new(domains.ServersPopupAction)).
|
||||
EndData().
|
||||
|
||||
//
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
32
EdgeUser/internal/web/actions/default/dns/providerOptions.go
Normal file
32
EdgeUser/internal/web/actions/default/dns/providerOptions.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// 服务商选项
|
||||
type ProviderOptionsAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *ProviderOptionsAction) RunPost(params struct {
|
||||
Type string
|
||||
}) {
|
||||
providersResp, err := this.RPC().DNSProviderRPC().FindAllEnabledDNSProvidersWithType(this.UserContext(), &pb.FindAllEnabledDNSProvidersWithTypeRequest{ProviderTypeCode: params.Type})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
providerMaps := []maps.Map{}
|
||||
for _, provider := range providersResp.DnsProviders {
|
||||
providerMaps = append(providerMaps, maps.Map{
|
||||
"id": provider.Id,
|
||||
"name": provider.Name,
|
||||
})
|
||||
}
|
||||
this.Data["providers"] = providerMaps
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package providers
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
)
|
||||
|
||||
type CreatePopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunGet(params struct{}) {
|
||||
// 所有厂商
|
||||
typesResp, err := this.RPC().DNSProviderRPC().FindAllDNSProviderTypes(this.UserContext(), &pb.FindAllDNSProviderTypesRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
typeMaps := []maps.Map{}
|
||||
for _, t := range typesResp.ProviderTypes {
|
||||
typeMaps = append(typeMaps, maps.Map{
|
||||
"name": t.Name,
|
||||
"code": t.Code,
|
||||
"description": t.Description,
|
||||
})
|
||||
}
|
||||
this.Data["types"] = typeMaps
|
||||
|
||||
// 自动生成CustomHTTP私钥
|
||||
this.Data["paramCustomHTTPSecret"] = rands.HexString(32)
|
||||
|
||||
// EdgeDNS集群列表
|
||||
nsClustersResp, err := this.RPC().NSClusterRPC().FindAllNSClusters(this.UserContext(), &pb.FindAllNSClustersRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var nsClusterMaps = []maps.Map{}
|
||||
for _, nsCluster := range nsClustersResp.NsClusters {
|
||||
nsClusterMaps = append(nsClusterMaps, maps.Map{
|
||||
"id": nsCluster.Id,
|
||||
"name": nsCluster.Name,
|
||||
})
|
||||
}
|
||||
this.Data["nsClusters"] = nsClusterMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunPost(params struct {
|
||||
Name string
|
||||
Type string
|
||||
|
||||
// DNSPod
|
||||
ParamDNSPodId string
|
||||
ParamDNSPodToken string
|
||||
ParamDNSPodRegion string
|
||||
|
||||
ParamDNSPodAPIType string
|
||||
ParamDNSPodAccessKeyId string
|
||||
ParamDNSPodAccessKeySecret string
|
||||
|
||||
// AliDNS
|
||||
ParamAliDNSAccessKeyId string
|
||||
ParamAliDNSAccessKeySecret string
|
||||
ParamAliDNSRegionId string
|
||||
|
||||
// HuaweiDNS
|
||||
ParamHuaweiAccessKeyId string
|
||||
ParamHuaweiAccessKeySecret string
|
||||
ParamHuaweiEndpoint string
|
||||
|
||||
// CloudFlare
|
||||
ParamCloudFlareAPIKey string
|
||||
ParamCloudFlareEmail string
|
||||
|
||||
// GoDaddy
|
||||
ParamGoDaddyKey string
|
||||
ParamGoDaddySecret string
|
||||
|
||||
// ClouDNS
|
||||
ParamClouDNSAuthId string
|
||||
ParamClouDNSSubAuthId string
|
||||
ParamClouDNSAuthPassword string
|
||||
|
||||
// DNS.COM
|
||||
ParamDNSComKey string
|
||||
ParamDNSComSecret string
|
||||
|
||||
// DNS.LA
|
||||
ParamDNSLaAPIId string
|
||||
ParamDNSLaSecret string
|
||||
|
||||
// VolcEngine
|
||||
ParamVolcEngineAccessKeyId string
|
||||
ParamVolcEngineAccessKeySecret string
|
||||
|
||||
// Amazon Route 53
|
||||
ParamAmazonRoute53AccessKeyId string
|
||||
ParamAmazonRoute53AccessKeySecret string
|
||||
ParamAmazonRoute53Region string
|
||||
|
||||
// Microsoft Azure DNS
|
||||
ParamAzureDNSSubscriptionId string
|
||||
ParamAzureDNSTenantId string
|
||||
ParamAzureDNSClientId string
|
||||
ParamAzureDNSClientSecret string
|
||||
ParamAzureDNSResourceGroupName string
|
||||
|
||||
// bunny.net
|
||||
ParamBunnyNetAPIKey string
|
||||
|
||||
// Gname
|
||||
ParamGnameAppid string
|
||||
ParamGnameSecret string
|
||||
|
||||
// Local EdgeDNS
|
||||
ParamLocalEdgeDNSClusterId int64
|
||||
|
||||
// EdgeDNS API
|
||||
ParamEdgeDNSAPIHost string
|
||||
ParamEdgeDNSAPIRole string
|
||||
ParamEdgeDNSAPIAccessKeyId string
|
||||
ParamEdgeDNSAPIAccessKeySecret string
|
||||
|
||||
// CustomHTTP
|
||||
ParamCustomHTTPURL string
|
||||
ParamCustomHTTPSecret string
|
||||
|
||||
MinTTL int32
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入账号说明").
|
||||
Field("type", params.Type).
|
||||
Require("请选择服务商厂家")
|
||||
|
||||
var apiParams = maps.Map{}
|
||||
switch params.Type {
|
||||
case "dnspod":
|
||||
apiParams["apiType"] = params.ParamDNSPodAPIType
|
||||
switch params.ParamDNSPodAPIType {
|
||||
case "tencentDNS":
|
||||
params.Must.
|
||||
Field("paramDNSPodAccessKeyId", params.ParamDNSPodAccessKeyId).
|
||||
Require("请输入SecretId").
|
||||
Field("paramDNSPodAccessKeySecret", params.ParamDNSPodAccessKeySecret).
|
||||
Require("请输入SecretKey")
|
||||
apiParams["accessKeyId"] = params.ParamDNSPodAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamDNSPodAccessKeySecret
|
||||
apiParams["region"] = params.ParamDNSPodRegion
|
||||
default:
|
||||
params.Must.
|
||||
Field("paramId", params.ParamDNSPodId).
|
||||
Require("请输入密钥ID").
|
||||
Field("paramToken", params.ParamDNSPodToken).
|
||||
Require("请输入密钥Token")
|
||||
|
||||
apiParams["id"] = params.ParamDNSPodId
|
||||
apiParams["token"] = params.ParamDNSPodToken
|
||||
apiParams["region"] = params.ParamDNSPodRegion
|
||||
}
|
||||
case "alidns":
|
||||
params.Must.
|
||||
Field("paramAliDNSAccessKeyId", params.ParamAliDNSAccessKeyId).
|
||||
Require("请输入AccessKeyId").
|
||||
Field("paramAliDNSAccessKeySecret", params.ParamAliDNSAccessKeySecret).
|
||||
Require("请输入AccessKeySecret")
|
||||
|
||||
apiParams["accessKeyId"] = params.ParamAliDNSAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamAliDNSAccessKeySecret
|
||||
apiParams["regionId"] = params.ParamAliDNSRegionId
|
||||
case "huaweiDNS":
|
||||
params.Must.
|
||||
Field("paramHuaweiAccessKeyId", params.ParamHuaweiAccessKeyId).
|
||||
Require("请输入AccessKeyId").
|
||||
Field("paramHuaweiAccessKeySecret", params.ParamHuaweiAccessKeySecret).
|
||||
Require("请输入AccessKeySecret")
|
||||
|
||||
apiParams["accessKeyId"] = params.ParamHuaweiAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamHuaweiAccessKeySecret
|
||||
apiParams["endpoint"] = params.ParamHuaweiEndpoint
|
||||
case "cloudFlare":
|
||||
params.Must.
|
||||
Field("paramCloudFlareAPIKey", params.ParamCloudFlareAPIKey).
|
||||
Require("请输入API密钥").
|
||||
Field("paramCloudFlareEmail", params.ParamCloudFlareEmail).
|
||||
Email("请输入正确格式的邮箱地址")
|
||||
apiParams["apiKey"] = params.ParamCloudFlareAPIKey
|
||||
apiParams["email"] = params.ParamCloudFlareEmail
|
||||
case "godaddy":
|
||||
params.Must.
|
||||
Field("paramGodaddyKey", params.ParamGoDaddyKey).
|
||||
Require("请输入Key").
|
||||
Field("paramGodaddySecret", params.ParamGoDaddySecret).
|
||||
Require("请输入Secret")
|
||||
apiParams["key"] = params.ParamGoDaddyKey
|
||||
apiParams["secret"] = params.ParamGoDaddySecret
|
||||
case "cloudns":
|
||||
var authIdString = params.ParamClouDNSAuthId
|
||||
var subAuthIdString = params.ParamClouDNSSubAuthId
|
||||
var authPassword = params.ParamClouDNSAuthPassword
|
||||
if len(authIdString) == 0 && len(subAuthIdString) == 0 {
|
||||
this.FailField("paramClouDNSAuthId", "请输入用户或者子用户的认证ID(auth-id)")
|
||||
}
|
||||
if len(authIdString) > 0 {
|
||||
if !regexp.MustCompile(`^\d+$`).MatchString(authIdString) {
|
||||
this.FailField("paramClouDNSAuthId", "用户认证ID需要是一个整数")
|
||||
}
|
||||
}
|
||||
if len(subAuthIdString) > 0 {
|
||||
if !regexp.MustCompile(`^\d+$`).MatchString(subAuthIdString) {
|
||||
this.FailField("paramClouDNSSubAuthId", "子用户认证ID需要是一个整数")
|
||||
}
|
||||
}
|
||||
if len(authPassword) == 0 {
|
||||
this.FailField("paramClouDNSPassword", "请输入用户或者子用户的认证密码")
|
||||
}
|
||||
|
||||
apiParams["authId"] = types.Int64(authIdString)
|
||||
apiParams["subAuthId"] = types.Int64(subAuthIdString)
|
||||
apiParams["authPassword"] = authPassword
|
||||
case "dnscom":
|
||||
params.Must.
|
||||
Field("paramDNSComKey", params.ParamDNSComKey).
|
||||
Require("请输入ApiKey").
|
||||
Field("paramDNSComSecret", params.ParamDNSComSecret).
|
||||
Require("请输入ApiSecret")
|
||||
|
||||
apiParams["key"] = params.ParamDNSComKey
|
||||
apiParams["secret"] = params.ParamDNSComSecret
|
||||
case "dnsla":
|
||||
params.Must.
|
||||
Field("paramDNSLaAPIId", params.ParamDNSLaAPIId).
|
||||
Require("请输入API ID").
|
||||
Field("paramDNSLaSecret", params.ParamDNSLaSecret).
|
||||
Require("请输入API密钥")
|
||||
|
||||
apiParams["apiId"] = params.ParamDNSLaAPIId
|
||||
apiParams["secret"] = params.ParamDNSLaSecret
|
||||
case "volcEngine":
|
||||
params.Must.
|
||||
Field("paramVolcEngineAccessKeyId", params.ParamVolcEngineAccessKeyId).
|
||||
Require("请输入Access Key ID").
|
||||
Field("paramVolcEngineAccessKeySecret", params.ParamVolcEngineAccessKeySecret).
|
||||
Require("请输入Secret Access Key")
|
||||
|
||||
apiParams["accessKeyId"] = params.ParamVolcEngineAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamVolcEngineAccessKeySecret
|
||||
case "amazonRoute53":
|
||||
params.Must.
|
||||
Field("paramAmazonRoute53AccessKeyId", params.ParamAmazonRoute53AccessKeyId).
|
||||
Require("请输入Access Key ID").
|
||||
Field("paramAmazonRoute53AccessKeySecret", params.ParamAmazonRoute53AccessKeySecret).
|
||||
Require("请输入Secret Access Key")
|
||||
|
||||
apiParams["accessKeyId"] = params.ParamAmazonRoute53AccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamAmazonRoute53AccessKeySecret
|
||||
apiParams["region"] = params.ParamAmazonRoute53Region
|
||||
case "azureDNS":
|
||||
params.Must.
|
||||
Field("paramAzureDNSSubscriptionId", params.ParamAzureDNSSubscriptionId).
|
||||
Require("请输入Subscription ID").
|
||||
Field("paramAzureDNSTenantId", params.ParamAzureDNSTenantId).
|
||||
Require("请输入Tenant ID").
|
||||
Field("paramAzureDNSClientId", params.ParamAzureDNSClientId).
|
||||
Require("请输入Client ID").
|
||||
Field("paramAzureDNSClientSecret", params.ParamAzureDNSClientSecret).
|
||||
Require("请输入Client Secret").
|
||||
Field("paramAzureDNSResourceGroupName", params.ParamAzureDNSResourceGroupName).
|
||||
Require("请输入Resource Group Name")
|
||||
|
||||
apiParams["subscriptionId"] = params.ParamAzureDNSSubscriptionId
|
||||
apiParams["tenantId"] = params.ParamAzureDNSTenantId
|
||||
apiParams["clientId"] = params.ParamAzureDNSClientId
|
||||
apiParams["clientSecret"] = params.ParamAzureDNSClientSecret
|
||||
apiParams["resourceGroupName"] = params.ParamAzureDNSResourceGroupName
|
||||
case "bunnyNet":
|
||||
params.Must.
|
||||
Field("paramBunnyNetAPIKey", params.ParamBunnyNetAPIKey).
|
||||
Require("请输入API密钥")
|
||||
apiParams["apiKey"] = params.ParamBunnyNetAPIKey
|
||||
case "gname":
|
||||
params.Must.
|
||||
Field("paramGnameAppid", params.ParamGnameAppid).
|
||||
Require("请输入APPID").
|
||||
Field("paramGnameSecret", params.ParamGnameSecret).
|
||||
Require("请输入API Secret")
|
||||
apiParams["appid"] = params.ParamGnameAppid
|
||||
apiParams["secret"] = params.ParamGnameSecret
|
||||
case "localEdgeDNS":
|
||||
params.Must.
|
||||
Field("paramLocalEdgeDNSClusterId", params.ParamLocalEdgeDNSClusterId).
|
||||
Gt(0, "请选择域名服务集群")
|
||||
apiParams["clusterId"] = params.ParamLocalEdgeDNSClusterId
|
||||
case "edgeDNSAPI":
|
||||
params.Must.
|
||||
Field("paramEdgeDNSAPIHost", params.ParamEdgeDNSAPIHost).
|
||||
Require("请输入API地址").
|
||||
Field("paramEdgeDNSAPIRole", params.ParamEdgeDNSAPIRole).
|
||||
Require("请选择AccessKey类型").
|
||||
Field("paramEdgeDNSAPIAccessKeyId", params.ParamEdgeDNSAPIAccessKeyId).
|
||||
Require("请输入AccessKey ID").
|
||||
Field("paramEdgeDNSAPIAccessKeySecret", params.ParamEdgeDNSAPIAccessKeySecret).
|
||||
Require("请输入AccessKey密钥")
|
||||
apiParams["host"] = params.ParamEdgeDNSAPIHost
|
||||
apiParams["role"] = params.ParamEdgeDNSAPIRole
|
||||
apiParams["accessKeyId"] = params.ParamEdgeDNSAPIAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamEdgeDNSAPIAccessKeySecret
|
||||
case "customHTTP":
|
||||
params.Must.
|
||||
Field("paramCustomHTTPURL", params.ParamCustomHTTPURL).
|
||||
Require("请输入HTTP URL").
|
||||
Match("^(?i)(http|https):", "URL必须以http://或者https://开头").
|
||||
Field("paramCustomHTTPSecret", params.ParamCustomHTTPSecret).
|
||||
Require("请输入私钥")
|
||||
apiParams["url"] = params.ParamCustomHTTPURL
|
||||
apiParams["secret"] = params.ParamCustomHTTPSecret
|
||||
default:
|
||||
this.Fail("暂时不支持此服务商'" + params.Type + "'")
|
||||
}
|
||||
|
||||
createResp, err := this.RPC().DNSProviderRPC().CreateDNSProvider(this.UserContext(), &pb.CreateDNSProviderRequest{
|
||||
Name: params.Name,
|
||||
Type: params.Type,
|
||||
ApiParamsJSON: apiParams.AsJSON(),
|
||||
MinTTL: params.MinTTL,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer this.CreateLogInfo(codes.DNSProvider_LogCreateDNSProvider, createResp.DnsProviderId)
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils/numberutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
type DeleteAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *DeleteAction) RunPost(params struct {
|
||||
ProviderId int64
|
||||
}) {
|
||||
// TODO 检查权限
|
||||
|
||||
// 记录日志
|
||||
defer this.CreateLogInfo(codes.DNSProvider_LogDeleteDNSProvider, params.ProviderId)
|
||||
|
||||
// 检查是否被集群使用
|
||||
countClustersResp, err := this.RPC().NodeClusterRPC().CountAllEnabledNodeClustersWithDNSProviderId(this.UserContext(), &pb.CountAllEnabledNodeClustersWithDNSProviderIdRequest{DnsProviderId: params.ProviderId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if countClustersResp.Count > 0 {
|
||||
this.Fail("当前DNS服务商账号正在被" + numberutils.FormatInt64(countClustersResp.Count) + "个集群所使用,所以不能删除。请修改集群设置后再操作。")
|
||||
}
|
||||
|
||||
// 判断是否被ACME任务使用
|
||||
countACMETasksResp, err := this.RPC().ACMETaskRPC().CountEnabledACMETasksWithDNSProviderId(this.UserContext(), &pb.CountEnabledACMETasksWithDNSProviderIdRequest{DnsProviderId: params.ProviderId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if countACMETasksResp.Count > 0 {
|
||||
this.Fail("当前DNS服务商账号正在被" + numberutils.FormatInt64(countACMETasksResp.Count) + "个ACME证书申请任务使用,所以不能删除。请修改ACME证书申请任务后再操作。")
|
||||
}
|
||||
|
||||
// 执行删除
|
||||
_, err = this.RPC().DNSProviderRPC().DeleteDNSProvider(this.UserContext(), &pb.DeleteDNSProviderRequest{DnsProviderId: params.ProviderId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
113
EdgeUser/internal/web/actions/default/dns/providers/index.go
Normal file
113
EdgeUser/internal/web/actions/default/dns/providers/index.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "provider")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
Keyword string
|
||||
Domain string
|
||||
ProviderType string
|
||||
}) {
|
||||
this.Data["keyword"] = params.Keyword
|
||||
this.Data["domain"] = params.Domain
|
||||
this.Data["providerType"] = params.ProviderType
|
||||
|
||||
// 格式化域名
|
||||
var domain = regexp.MustCompile(`^(www\.)`).ReplaceAllString(params.Domain, "")
|
||||
domain = strings.ToLower(domain)
|
||||
|
||||
countResp, err := this.RPC().DNSProviderRPC().CountAllEnabledDNSProviders(this.UserContext(), &pb.CountAllEnabledDNSProvidersRequest{
|
||||
UserId: this.UserId(),
|
||||
Keyword: params.Keyword,
|
||||
Domain: domain,
|
||||
Type: params.ProviderType,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
count := countResp.Count
|
||||
page := this.NewPage(count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
providersResp, err := this.RPC().DNSProviderRPC().ListEnabledDNSProviders(this.UserContext(), &pb.ListEnabledDNSProvidersRequest{
|
||||
UserId: this.UserId(),
|
||||
Keyword: params.Keyword,
|
||||
Domain: domain,
|
||||
Type: params.ProviderType,
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var providerMaps = []maps.Map{}
|
||||
for _, provider := range providersResp.DnsProviders {
|
||||
dataUpdatedTime := ""
|
||||
if provider.DataUpdatedAt > 0 {
|
||||
dataUpdatedTime = timeutil.FormatTime("Y-m-d H:i:s", provider.DataUpdatedAt)
|
||||
}
|
||||
|
||||
// 域名
|
||||
countDomainsResp, err := this.RPC().DNSDomainRPC().CountAllDNSDomainsWithDNSProviderId(this.UserContext(), &pb.CountAllDNSDomainsWithDNSProviderIdRequest{
|
||||
DnsProviderId: provider.Id,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
countDomains := countDomainsResp.Count
|
||||
|
||||
providerMaps = append(providerMaps, maps.Map{
|
||||
"id": provider.Id,
|
||||
"name": provider.Name,
|
||||
"type": provider.Type,
|
||||
"typeName": provider.TypeName,
|
||||
"dataUpdatedTime": dataUpdatedTime,
|
||||
"countDomains": countDomains,
|
||||
})
|
||||
}
|
||||
this.Data["providers"] = providerMaps
|
||||
|
||||
// 类型
|
||||
typesResponse, err := this.RPC().DNSProviderRPC().FindAllDNSProviderTypes(this.UserContext(), &pb.FindAllDNSProviderTypesRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var providerTypeMaps = []maps.Map{}
|
||||
for _, providerType := range typesResponse.ProviderTypes {
|
||||
countProvidersWithTypeResp, err := this.RPC().DNSProviderRPC().CountAllEnabledDNSProviders(this.UserContext(), &pb.CountAllEnabledDNSProvidersRequest{
|
||||
Type: providerType.Code,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if countProvidersWithTypeResp.Count > 0 {
|
||||
providerTypeMaps = append(providerTypeMaps, maps.Map{
|
||||
"name": providerType.Name,
|
||||
"code": providerType.Code,
|
||||
"count": countProvidersWithTypeResp.Count,
|
||||
})
|
||||
}
|
||||
}
|
||||
this.Data["providerTypes"] = providerTypeMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
116
EdgeUser/internal/web/actions/default/dns/providers/provider.go
Normal file
116
EdgeUser/internal/web/actions/default/dns/providers/provider.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
type ProviderAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *ProviderAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *ProviderAction) RunGet(params struct {
|
||||
ProviderId int64
|
||||
Page int
|
||||
Filter string
|
||||
}) {
|
||||
this.Data["pageNo"] = params.Page
|
||||
this.Data["filter"] = params.Filter
|
||||
|
||||
providerResp, err := this.RPC().DNSProviderRPC().FindEnabledDNSProvider(this.UserContext(), &pb.FindEnabledDNSProviderRequest{DnsProviderId: params.ProviderId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
provider := providerResp.DnsProvider
|
||||
if provider == nil {
|
||||
this.NotFound("dnsProvider", params.ProviderId)
|
||||
return
|
||||
}
|
||||
|
||||
apiParams := maps.Map{}
|
||||
if len(provider.ApiParamsJSON) > 0 {
|
||||
err = json.Unmarshal(provider.ApiParamsJSON, &apiParams)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 本地EdgeDNS相关
|
||||
localEdgeDNSMap, err := this.readEdgeDNS(provider, apiParams)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["provider"] = maps.Map{
|
||||
"id": provider.Id,
|
||||
"name": provider.Name,
|
||||
"type": provider.Type,
|
||||
"typeName": provider.TypeName,
|
||||
"apiParams": apiParams,
|
||||
"localEdgeDNS": localEdgeDNSMap,
|
||||
"minTTL": provider.MinTTL,
|
||||
}
|
||||
|
||||
// 域名数量
|
||||
countDomainsResp, err := this.RPC().DNSDomainRPC().CountAllDNSDomainsWithDNSProviderId(this.UserContext(), &pb.CountAllDNSDomainsWithDNSProviderIdRequest{
|
||||
DnsProviderId: params.ProviderId,
|
||||
IsDeleted: params.Filter == "deleted",
|
||||
IsDown: params.Filter == "down",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var countDomains = countDomainsResp.Count
|
||||
var page = this.NewPage(countDomains)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
// 域名
|
||||
domainsResp, err := this.RPC().DNSDomainRPC().ListBasicDNSDomainsWithDNSProviderId(this.UserContext(), &pb.ListBasicDNSDomainsWithDNSProviderIdRequest{
|
||||
DnsProviderId: provider.Id,
|
||||
IsDeleted: params.Filter == "deleted",
|
||||
IsDown: params.Filter == "down",
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
domainMaps := []maps.Map{}
|
||||
for _, domain := range domainsResp.DnsDomains {
|
||||
dataUpdatedTime := ""
|
||||
if domain.DataUpdatedAt > 0 {
|
||||
dataUpdatedTime = timeutil.FormatTime("Y-m-d H:i:s", domain.DataUpdatedAt)
|
||||
}
|
||||
domainMaps = append(domainMaps, maps.Map{
|
||||
"id": domain.Id,
|
||||
"name": domain.Name,
|
||||
"isOn": domain.IsOn,
|
||||
"isUp": domain.IsUp,
|
||||
"isDeleted": domain.IsDeleted,
|
||||
"dataUpdatedTime": dataUpdatedTime,
|
||||
"countRoutes": len(domain.Routes),
|
||||
"countServerRecords": domain.CountServerRecords,
|
||||
"serversChanged": domain.ServersChanged,
|
||||
"countNodeRecords": domain.CountNodeRecords,
|
||||
"nodesChanged": domain.NodesChanged,
|
||||
"countClusters": domain.CountNodeClusters,
|
||||
"countAllNodes": domain.CountAllNodes,
|
||||
"countAllServers": domain.CountAllServers,
|
||||
})
|
||||
}
|
||||
this.Data["domains"] = domainMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package providers
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
func (this *ProviderAction) readEdgeDNS(provider *pb.DNSProvider, apiParams maps.Map) (maps.Map, error) {
|
||||
var localEdgeDNSMap = maps.Map{}
|
||||
|
||||
if provider.Type == "localEdgeDNS" {
|
||||
nsClusterId := apiParams.GetInt64("clusterId")
|
||||
nsClusterResp, err := this.RPC().NSClusterRPC().FindNSCluster(this.UserContext(), &pb.FindNSClusterRequest{NsClusterId: nsClusterId})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nsCluster := nsClusterResp.NsCluster
|
||||
if nsCluster != nil {
|
||||
localEdgeDNSMap = maps.Map{
|
||||
"id": nsCluster.Id,
|
||||
"name": nsCluster.Name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return localEdgeDNSMap, nil
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package providers
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
type SyncDomainsAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *SyncDomainsAction) RunPost(params struct {
|
||||
ProviderId int64
|
||||
}) {
|
||||
resp, err := this.RPC().DNSDomainRPC().SyncDNSDomainsFromProvider(this.UserContext(), &pb.SyncDNSDomainsFromProviderRequest{DnsProviderId: params.ProviderId})
|
||||
if err != nil {
|
||||
this.Fail("更新域名失败:" + err.Error())
|
||||
}
|
||||
|
||||
this.Data["hasChanges"] = resp.HasChanges
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type UpdatePopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpdatePopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *UpdatePopupAction) RunGet(params struct {
|
||||
ProviderId int64
|
||||
}) {
|
||||
providerResp, err := this.RPC().DNSProviderRPC().FindEnabledDNSProvider(this.UserContext(), &pb.FindEnabledDNSProviderRequest{DnsProviderId: params.ProviderId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var provider = providerResp.DnsProvider
|
||||
if provider == nil {
|
||||
this.NotFound("dnsProvider", params.ProviderId)
|
||||
return
|
||||
}
|
||||
|
||||
var apiParams = maps.Map{}
|
||||
if len(provider.ApiParamsJSON) > 0 {
|
||||
err = json.Unmarshal(provider.ApiParamsJSON, &apiParams)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["provider"] = maps.Map{
|
||||
"id": provider.Id,
|
||||
"name": provider.Name,
|
||||
"type": provider.Type,
|
||||
"typeName": provider.TypeName,
|
||||
"minTTL": provider.MinTTL,
|
||||
"params": apiParams,
|
||||
}
|
||||
|
||||
// 所有厂商
|
||||
typesResp, err := this.RPC().DNSProviderRPC().FindAllDNSProviderTypes(this.UserContext(), &pb.FindAllDNSProviderTypesRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var typeMaps = []maps.Map{}
|
||||
for _, t := range typesResp.ProviderTypes {
|
||||
typeMaps = append(typeMaps, maps.Map{
|
||||
"name": t.Name,
|
||||
"code": t.Code,
|
||||
"description": t.Description,
|
||||
})
|
||||
}
|
||||
this.Data["types"] = typeMaps
|
||||
|
||||
// EdgeDNS集群列表
|
||||
nsClustersResp, err := this.RPC().NSClusterRPC().FindAllNSClusters(this.UserContext(), &pb.FindAllNSClustersRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var nsClusterMaps = []maps.Map{}
|
||||
for _, nsCluster := range nsClustersResp.NsClusters {
|
||||
nsClusterMaps = append(nsClusterMaps, maps.Map{
|
||||
"id": nsCluster.Id,
|
||||
"name": nsCluster.Name,
|
||||
})
|
||||
}
|
||||
this.Data["nsClusters"] = nsClusterMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *UpdatePopupAction) RunPost(params struct {
|
||||
ProviderId int64
|
||||
|
||||
Name string
|
||||
Type string
|
||||
|
||||
// DNSPod
|
||||
ParamDNSPodId string
|
||||
ParamDNSPodToken string
|
||||
ParamDNSPodRegion string
|
||||
|
||||
ParamDNSPodAPIType string
|
||||
ParamDNSPodAccessKeyId string
|
||||
ParamDNSPodAccessKeySecret string
|
||||
|
||||
// AliDNS
|
||||
ParamAliDNSAccessKeyId string
|
||||
ParamAliDNSAccessKeySecret string
|
||||
ParamAliDNSRegionId string
|
||||
|
||||
// HuaweiDNS
|
||||
ParamHuaweiAccessKeyId string
|
||||
ParamHuaweiAccessKeySecret string
|
||||
ParamHuaweiEndpoint string
|
||||
|
||||
// DNS.COM
|
||||
ParamApiKey string
|
||||
ParamApiSecret string
|
||||
|
||||
// CloudFlare
|
||||
ParamCloudFlareAPIKey string
|
||||
ParamCloudFlareEmail string
|
||||
|
||||
// GoDaddy
|
||||
ParamGoDaddyKey string
|
||||
ParamGoDaddySecret string
|
||||
|
||||
// ClouDNS
|
||||
ParamClouDNSAuthId string
|
||||
ParamClouDNSSubAuthId string
|
||||
ParamClouDNSAuthPassword string
|
||||
|
||||
// DNS.COM
|
||||
ParamDNSComKey string
|
||||
ParamDNSComSecret string
|
||||
|
||||
// DNS.LA
|
||||
ParamDNSLaAPIId string
|
||||
ParamDNSLaSecret string
|
||||
|
||||
// VolcEngine
|
||||
ParamVolcEngineAccessKeyId string
|
||||
ParamVolcEngineAccessKeySecret string
|
||||
|
||||
// Amazon Route 53
|
||||
ParamAmazonRoute53AccessKeyId string
|
||||
ParamAmazonRoute53AccessKeySecret string
|
||||
ParamAmazonRoute53Region string
|
||||
|
||||
// Microsoft Azure DNS
|
||||
ParamAzureDNSSubscriptionId string
|
||||
ParamAzureDNSTenantId string
|
||||
ParamAzureDNSClientId string
|
||||
ParamAzureDNSClientSecret string
|
||||
ParamAzureDNSResourceGroupName string
|
||||
|
||||
// bunny.net
|
||||
ParamBunnyNetAPIKey string
|
||||
|
||||
// Gname
|
||||
ParamGnameAppid string
|
||||
ParamGnameSecret string
|
||||
|
||||
// Local EdgeDNS
|
||||
ParamLocalEdgeDNSClusterId int64
|
||||
|
||||
// EdgeDNS API
|
||||
ParamEdgeDNSAPIHost string
|
||||
ParamEdgeDNSAPIRole string
|
||||
ParamEdgeDNSAPIAccessKeyId string
|
||||
ParamEdgeDNSAPIAccessKeySecret string
|
||||
|
||||
// CustomHTTP
|
||||
ParamCustomHTTPURL string
|
||||
ParamCustomHTTPSecret string
|
||||
|
||||
MinTTL int32
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
defer this.CreateLogInfo(codes.DNSProvider_LogUpdateDNSProvider, params.ProviderId)
|
||||
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入账号说明").
|
||||
Field("type", params.Type).
|
||||
Require("请选择服务商厂家")
|
||||
|
||||
var apiParams = maps.Map{}
|
||||
switch params.Type {
|
||||
case "dnspod":
|
||||
apiParams["apiType"] = params.ParamDNSPodAPIType
|
||||
switch params.ParamDNSPodAPIType {
|
||||
case "tencentDNS":
|
||||
params.Must.
|
||||
Field("paramDNSPodAccessKeyId", params.ParamDNSPodAccessKeyId).
|
||||
Require("请输入SecretId").
|
||||
Field("paramDNSPodAccessKeySecret", params.ParamDNSPodAccessKeySecret).
|
||||
Require("请输入SecretKey")
|
||||
apiParams["accessKeyId"] = params.ParamDNSPodAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamDNSPodAccessKeySecret
|
||||
apiParams["region"] = params.ParamDNSPodRegion
|
||||
default:
|
||||
params.Must.
|
||||
Field("paramId", params.ParamDNSPodId).
|
||||
Require("请输入密钥ID").
|
||||
Field("paramToken", params.ParamDNSPodToken).
|
||||
Require("请输入密钥Token")
|
||||
|
||||
apiParams["id"] = params.ParamDNSPodId
|
||||
apiParams["token"] = params.ParamDNSPodToken
|
||||
apiParams["region"] = params.ParamDNSPodRegion
|
||||
}
|
||||
case "alidns":
|
||||
params.Must.
|
||||
Field("paramAliDNSAccessKeyId", params.ParamAliDNSAccessKeyId).
|
||||
Require("请输入AccessKeyId").
|
||||
Field("paramAliDNSAccessKeySecret", params.ParamAliDNSAccessKeySecret).
|
||||
Require("请输入AccessKeySecret")
|
||||
|
||||
apiParams["accessKeyId"] = params.ParamAliDNSAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamAliDNSAccessKeySecret
|
||||
apiParams["regionId"] = params.ParamAliDNSRegionId
|
||||
case "huaweiDNS":
|
||||
params.Must.
|
||||
Field("paramHuaweiAccessKeyId", params.ParamHuaweiAccessKeyId).
|
||||
Require("请输入AccessKeyId").
|
||||
Field("paramHuaweiAccessKeySecret", params.ParamHuaweiAccessKeySecret).
|
||||
Require("请输入AccessKeySecret")
|
||||
|
||||
apiParams["accessKeyId"] = params.ParamHuaweiAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamHuaweiAccessKeySecret
|
||||
apiParams["endpoint"] = params.ParamHuaweiEndpoint
|
||||
case "cloudFlare":
|
||||
params.Must.
|
||||
Field("paramCloudFlareAPIKey", params.ParamCloudFlareAPIKey).
|
||||
Require("请输入API密钥").
|
||||
Field("paramCloudFlareEmail", params.ParamCloudFlareEmail).
|
||||
Email("请输入正确格式的邮箱地址")
|
||||
apiParams["apiKey"] = params.ParamCloudFlareAPIKey
|
||||
apiParams["email"] = params.ParamCloudFlareEmail
|
||||
case "godaddy":
|
||||
params.Must.
|
||||
Field("paramGodaddyKey", params.ParamGoDaddyKey).
|
||||
Require("请输入Key").
|
||||
Field("paramGodaddySecret", params.ParamGoDaddySecret).
|
||||
Require("请输入Secret")
|
||||
apiParams["key"] = params.ParamGoDaddyKey
|
||||
apiParams["secret"] = params.ParamGoDaddySecret
|
||||
case "cloudns":
|
||||
var authIdString = params.ParamClouDNSAuthId
|
||||
var subAuthIdString = params.ParamClouDNSSubAuthId
|
||||
var authPassword = params.ParamClouDNSAuthPassword
|
||||
if len(authIdString) == 0 && len(subAuthIdString) == 0 {
|
||||
this.FailField("paramClouDNSAuthId", "请输入用户或者子用户的认证ID(auth-id)")
|
||||
}
|
||||
if len(authIdString) > 0 {
|
||||
if !regexp.MustCompile(`^\d+$`).MatchString(authIdString) {
|
||||
this.FailField("paramClouDNSAuthId", "用户认证ID需要是一个整数")
|
||||
}
|
||||
}
|
||||
if len(subAuthIdString) > 0 {
|
||||
if !regexp.MustCompile(`^\d+$`).MatchString(subAuthIdString) {
|
||||
this.FailField("paramClouDNSSubAuthId", "子用户认证ID需要是一个整数")
|
||||
}
|
||||
}
|
||||
if len(authPassword) == 0 {
|
||||
this.FailField("paramClouDNSPassword", "请输入用户或者子用户的认证密码")
|
||||
}
|
||||
|
||||
apiParams["authId"] = types.Int64(authIdString)
|
||||
apiParams["subAuthId"] = types.Int64(subAuthIdString)
|
||||
apiParams["authPassword"] = authPassword
|
||||
case "dnscom":
|
||||
params.Must.
|
||||
Field("paramDNSComKey", params.ParamDNSComKey).
|
||||
Require("请输入ApiKey").
|
||||
Field("paramDNSComSecret", params.ParamDNSComSecret).
|
||||
Require("请输入ApiSecret")
|
||||
|
||||
apiParams["key"] = params.ParamDNSComKey
|
||||
apiParams["secret"] = params.ParamDNSComSecret
|
||||
case "dnsla":
|
||||
params.Must.
|
||||
Field("paramDNSLaAPIId", params.ParamDNSLaAPIId).
|
||||
Require("请输入API ID").
|
||||
Field("paramDNSLaSecret", params.ParamDNSLaSecret).
|
||||
Require("请输入API密钥")
|
||||
|
||||
apiParams["apiId"] = params.ParamDNSLaAPIId
|
||||
apiParams["secret"] = params.ParamDNSLaSecret
|
||||
case "volcEngine":
|
||||
params.Must.
|
||||
Field("paramVolcEngineAccessKeyId", params.ParamVolcEngineAccessKeyId).
|
||||
Require("请输入Access Key ID").
|
||||
Field("paramVolcEngineAccessKeySecret", params.ParamVolcEngineAccessKeySecret).
|
||||
Require("请输入Secret Access Key")
|
||||
|
||||
apiParams["accessKeyId"] = params.ParamVolcEngineAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamVolcEngineAccessKeySecret
|
||||
case "amazonRoute53":
|
||||
params.Must.
|
||||
Field("paramAmazonRoute53AccessKeyId", params.ParamAmazonRoute53AccessKeyId).
|
||||
Require("请输入Access Key ID").
|
||||
Field("paramAmazonRoute53AccessKeySecret", params.ParamAmazonRoute53AccessKeySecret).
|
||||
Require("请输入Secret Access Key")
|
||||
|
||||
apiParams["accessKeyId"] = params.ParamAmazonRoute53AccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamAmazonRoute53AccessKeySecret
|
||||
apiParams["region"] = params.ParamAmazonRoute53Region
|
||||
case "azureDNS":
|
||||
params.Must.
|
||||
Field("paramAzureDNSSubscriptionId", params.ParamAzureDNSSubscriptionId).
|
||||
Require("请输入Subscription ID").
|
||||
Field("paramAzureDNSTenantId", params.ParamAzureDNSTenantId).
|
||||
Require("请输入Tenant ID").
|
||||
Field("paramAzureDNSClientId", params.ParamAzureDNSClientId).
|
||||
Require("请输入Client ID").
|
||||
Field("paramAzureDNSClientSecret", params.ParamAzureDNSClientSecret).
|
||||
Require("请输入Client Secret").
|
||||
Field("paramAzureDNSResourceGroupName", params.ParamAzureDNSResourceGroupName).
|
||||
Require("请输入Resource Group Name")
|
||||
|
||||
apiParams["subscriptionId"] = params.ParamAzureDNSSubscriptionId
|
||||
apiParams["tenantId"] = params.ParamAzureDNSTenantId
|
||||
apiParams["clientId"] = params.ParamAzureDNSClientId
|
||||
apiParams["clientSecret"] = params.ParamAzureDNSClientSecret
|
||||
apiParams["resourceGroupName"] = params.ParamAzureDNSResourceGroupName
|
||||
case "bunnyNet":
|
||||
params.Must.
|
||||
Field("paramBunnyNetAPIKey", params.ParamBunnyNetAPIKey).
|
||||
Require("请输入API密钥")
|
||||
apiParams["apiKey"] = params.ParamBunnyNetAPIKey
|
||||
case "gname":
|
||||
params.Must.
|
||||
Field("paramGnameAppid", params.ParamGnameAppid).
|
||||
Require("请输入APPID").
|
||||
Field("paramGnameSecret", params.ParamGnameSecret).
|
||||
Require("请输入API Secret")
|
||||
apiParams["appid"] = params.ParamGnameAppid
|
||||
apiParams["secret"] = params.ParamGnameSecret
|
||||
case "localEdgeDNS":
|
||||
params.Must.
|
||||
Field("paramLocalEdgeDNSClusterId", params.ParamLocalEdgeDNSClusterId).
|
||||
Gt(0, "请选择域名服务集群")
|
||||
apiParams["clusterId"] = params.ParamLocalEdgeDNSClusterId
|
||||
case "edgeDNSAPI":
|
||||
params.Must.
|
||||
Field("paramEdgeDNSAPIHost", params.ParamEdgeDNSAPIHost).
|
||||
Require("请输入API地址").
|
||||
Field("paramEdgeDNSAPIRole", params.ParamEdgeDNSAPIRole).
|
||||
Require("请选择AccessKey类型").
|
||||
Field("paramEdgeDNSAPIAccessKeyId", params.ParamEdgeDNSAPIAccessKeyId).
|
||||
Require("请输入AccessKey ID").
|
||||
Field("paramEdgeDNSAPIAccessKeySecret", params.ParamEdgeDNSAPIAccessKeySecret).
|
||||
Require("请输入AccessKey密钥")
|
||||
apiParams["host"] = params.ParamEdgeDNSAPIHost
|
||||
apiParams["role"] = params.ParamEdgeDNSAPIRole
|
||||
apiParams["accessKeyId"] = params.ParamEdgeDNSAPIAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamEdgeDNSAPIAccessKeySecret
|
||||
case "customHTTP":
|
||||
params.Must.
|
||||
Field("paramCustomHTTPURL", params.ParamCustomHTTPURL).
|
||||
Require("请输入HTTP URL").
|
||||
Match("^(?i)(http|https):", "URL必须以http://或者https://开头").
|
||||
Field("paramCustomHTTPSecret", params.ParamCustomHTTPSecret).
|
||||
Require("请输入私钥")
|
||||
apiParams["url"] = params.ParamCustomHTTPURL
|
||||
apiParams["secret"] = params.ParamCustomHTTPSecret
|
||||
default:
|
||||
this.Fail("暂时不支持此服务商'" + params.Type + "'")
|
||||
}
|
||||
|
||||
_, err := this.RPC().DNSProviderRPC().UpdateDNSProvider(this.UserContext(), &pb.UpdateDNSProviderRequest{
|
||||
DnsProviderId: params.ProviderId,
|
||||
Name: params.Name,
|
||||
MinTTL: params.MinTTL,
|
||||
ApiParamsJSON: apiParams.AsJSON(),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
114
EdgeUser/internal/web/actions/default/docs/docutils/cache.go
Normal file
114
EdgeUser/internal/web/actions/default/docs/docutils/cache.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package docutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
|
||||
"github.com/yuin/goldmark"
|
||||
highlighting "github.com/yuin/goldmark-highlighting/v2"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/renderer/html"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type cacheItem struct {
|
||||
ModifiedAt int64
|
||||
Data []byte
|
||||
}
|
||||
|
||||
var cacheMap = map[string]*cacheItem{} // path => *cacheItem
|
||||
var cacheLocker = &sync.RWMutex{}
|
||||
|
||||
func ReadMarkdownFile(path string, productName string, rootURL string) ([]byte, error) {
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if stat.IsDir() {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
var modifiedAt = stat.ModTime().Unix()
|
||||
|
||||
cacheLocker.RLock()
|
||||
item, ok := cacheMap[path]
|
||||
if ok && item.ModifiedAt == modifiedAt {
|
||||
cacheLocker.RUnlock()
|
||||
return item.Data, nil
|
||||
}
|
||||
cacheLocker.RUnlock()
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// keywords
|
||||
data = bytes.ReplaceAll(data, []byte("GoEdge"), []byte(productName))
|
||||
data = bytes.ReplaceAll(data, []byte("GOEDGE"), []byte(productName))
|
||||
data = bytes.ReplaceAll(data, []byte("http://goedge.cn"), []byte("http://example.com"))
|
||||
data = bytes.ReplaceAll(data, []byte("https://goedge.cn"), []byte("https://example.com"))
|
||||
|
||||
var markdown = goldmark.New(
|
||||
goldmark.WithParserOptions(
|
||||
parser.WithAutoHeadingID(), // read note
|
||||
),
|
||||
goldmark.WithExtensions(extension.Table,
|
||||
extension.Strikethrough,
|
||||
highlighting.NewHighlighting(highlighting.WithStyle("github"), highlighting.WithFormatOptions(chromahtml.TabWidth(4))),
|
||||
),
|
||||
goldmark.WithRendererOptions(html.WithHardWraps()),
|
||||
)
|
||||
|
||||
var buf = &bytes.Buffer{}
|
||||
err = markdown.Convert(data, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = buf.Bytes()
|
||||
|
||||
// convert links
|
||||
{
|
||||
var reg = regexp.MustCompile(`(?U)<a href="(.+\.md)">`)
|
||||
data = reg.ReplaceAllFunc(data, func(link []byte) []byte {
|
||||
var pieces = reg.FindSubmatch(link)
|
||||
if len(pieces) > 1 {
|
||||
var newLink = append([]byte(rootURL), bytes.TrimSuffix(pieces[1], []byte(".md"))...)
|
||||
newLink = append(newLink, []byte(".html")...)
|
||||
newLink = []byte(filepath.Clean(string(newLink)))
|
||||
return bytes.ReplaceAll(link, pieces[1], newLink)
|
||||
}
|
||||
return link
|
||||
})
|
||||
}
|
||||
|
||||
// convert images
|
||||
{
|
||||
var reg = regexp.MustCompile(`(?U)<img src="(.+)"[^>]*>`)
|
||||
data = reg.ReplaceAllFunc(data, func(link []byte) []byte {
|
||||
var pieces = reg.FindSubmatch(link)
|
||||
if len(pieces) > 1 {
|
||||
var newLink = append([]byte(rootURL), pieces[1]...)
|
||||
newLink = []byte(filepath.Clean(string(newLink)))
|
||||
return []byte("<a href=\"" + string(newLink) + "\" target=\"_blank\">" + string(bytes.ReplaceAll(link, pieces[1], newLink)) + "</a>")
|
||||
}
|
||||
return link
|
||||
})
|
||||
}
|
||||
|
||||
// put into cache
|
||||
item = &cacheItem{
|
||||
ModifiedAt: modifiedAt,
|
||||
Data: data,
|
||||
}
|
||||
cacheLocker.Lock()
|
||||
cacheMap[path] = item
|
||||
cacheLocker.Unlock()
|
||||
|
||||
return data, nil
|
||||
}
|
||||
304
EdgeUser/internal/web/actions/default/docs/index.go
Normal file
304
EdgeUser/internal/web/actions/default/docs/index.go
Normal file
@@ -0,0 +1,304 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package docs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/docs/docutils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/yuin/goldmark"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const URLPrefix = "/docs"
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
Page string
|
||||
}) {
|
||||
var docRoot = Tea.Root
|
||||
if Tea.IsTesting() {
|
||||
docRoot += "/../web"
|
||||
} else {
|
||||
docRoot += "/web"
|
||||
}
|
||||
docRoot += "/docs"
|
||||
|
||||
docRoot = filepath.Clean(docRoot)
|
||||
|
||||
var pageName = params.Page
|
||||
if strings.Contains(pageName, "..") { // prevent path traveling
|
||||
this.ResponseWriter.WriteHeader(http.StatusNotFound)
|
||||
this.WriteString("not found '" + params.Page + "'")
|
||||
return
|
||||
}
|
||||
if len(pageName) == 0 {
|
||||
pageName = "index.html"
|
||||
} else if strings.HasSuffix(pageName, "/") {
|
||||
pageName += "index.html"
|
||||
}
|
||||
|
||||
// extension
|
||||
var ext = filepath.Ext(pageName)
|
||||
switch ext {
|
||||
case ".html":
|
||||
this.doHTML(docRoot, pageName)
|
||||
case ".jpg", ".jpeg", ".png", "gif", ".webp":
|
||||
this.doImage(docRoot, pageName, ext)
|
||||
default:
|
||||
this.ResponseWriter.WriteHeader(http.StatusNotFound)
|
||||
this.WriteString("not found '" + params.Page + "'")
|
||||
}
|
||||
}
|
||||
|
||||
func (this *IndexAction) doHTML(docRoot string, pageName string) {
|
||||
pageName = strings.TrimSuffix(pageName, ".html")
|
||||
var path = filepath.Clean(docRoot + "/" + pageName + ".md")
|
||||
if !strings.HasPrefix(path, docRoot) {
|
||||
this.ResponseWriter.WriteHeader(http.StatusNotFound)
|
||||
this.WriteString("not found '" + pageName + "'")
|
||||
return
|
||||
}
|
||||
|
||||
// convert links
|
||||
var rootURL = URLPrefix + "/"
|
||||
var pageDir = filepath.Dir(pageName)
|
||||
if len(pageDir) > 0 && pageDir != "." {
|
||||
rootURL += pageDir + "/"
|
||||
}
|
||||
|
||||
resultData, err := docutils.ReadMarkdownFile(path, this.Data.GetString("teaName"), rootURL)
|
||||
if err != nil {
|
||||
this.ResponseWriter.WriteHeader(http.StatusNotFound)
|
||||
this.WriteString("not found '" + pageName + "'")
|
||||
return
|
||||
}
|
||||
|
||||
// toc
|
||||
{
|
||||
var reg = regexp.MustCompile(`(?U)<h(\d)\sid="(.+)">(.*)</h\d>`)
|
||||
var subMatches = reg.FindAllStringSubmatch(string(resultData), -1)
|
||||
var tocItem = &TOCItem{
|
||||
Depth: 0,
|
||||
}
|
||||
var lastItem *TOCItem
|
||||
for _, subMatch := range subMatches {
|
||||
var depth = types.Int(subMatch[1])
|
||||
var id = subMatch[2]
|
||||
var title = subMatch[3]
|
||||
var item = &TOCItem{
|
||||
Depth: depth,
|
||||
Id: id,
|
||||
Title: title,
|
||||
Children: []*TOCItem{},
|
||||
Parent: nil,
|
||||
}
|
||||
|
||||
if lastItem == nil {
|
||||
item.Parent = tocItem
|
||||
tocItem.Children = append(tocItem.Children, item)
|
||||
} else if lastItem.Depth == item.Depth {
|
||||
if lastItem.Parent != nil {
|
||||
item.Parent = lastItem.Parent
|
||||
lastItem.Parent.Children = append(lastItem.Parent.Children, item)
|
||||
}
|
||||
} else if lastItem.Depth < item.Depth {
|
||||
item.Parent = lastItem
|
||||
lastItem.Children = append(lastItem.Children, item)
|
||||
} else {
|
||||
var parent = lastItem.Parent
|
||||
for parent != nil {
|
||||
if parent.Depth == item.Depth {
|
||||
if parent.Parent != nil {
|
||||
item.Parent = parent.Parent
|
||||
parent.Parent.Children = append(parent.Parent.Children, item)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
parent = parent.Parent
|
||||
}
|
||||
}
|
||||
|
||||
lastItem = item
|
||||
}
|
||||
|
||||
// reset parent
|
||||
tocItem.UnsetParent()
|
||||
|
||||
if len(tocItem.Children) > 0 {
|
||||
this.Data["toc"] = tocItem.AsHTML()
|
||||
} else {
|
||||
this.Data["toc"] = ""
|
||||
}
|
||||
|
||||
// format <h1...6>
|
||||
/**resultData = reg.ReplaceAllFunc(resultData, func(i []byte) []byte {
|
||||
var subMatch = reg.FindSubmatch(i)
|
||||
if len(subMatch) > 1 {
|
||||
var depth = subMatch[1]
|
||||
var idData = subMatch[2]
|
||||
var title = subMatch[3]
|
||||
return []byte("<a class=\"anchor\" id=\"" + string(idData) + "\"> </a>\n<h" + string(depth) + ">" + string(title) + "</h" + string(depth) + ">")
|
||||
}
|
||||
return i
|
||||
})**/
|
||||
}
|
||||
|
||||
this.Data["content"] = string(resultData)
|
||||
|
||||
// 整体菜单
|
||||
this.readMenu(docRoot)
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *IndexAction) doImage(docRoot string, pageName string, ext string) {
|
||||
var path = filepath.Clean(docRoot + "/" + pageName)
|
||||
if !strings.HasPrefix(path, docRoot) {
|
||||
this.ResponseWriter.WriteHeader(http.StatusNotFound)
|
||||
this.WriteString("not found '" + pageName + "'")
|
||||
return
|
||||
}
|
||||
|
||||
var mimeType = ""
|
||||
switch ext {
|
||||
case ".jpg", ".jpeg":
|
||||
mimeType = "image/jpeg"
|
||||
case ".png":
|
||||
mimeType = "image/png"
|
||||
case ".webp":
|
||||
mimeType = "image/webp"
|
||||
case ".gif":
|
||||
mimeType = "image/gif"
|
||||
}
|
||||
|
||||
if len(mimeType) == 0 {
|
||||
this.ResponseWriter.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
this.ResponseWriter.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.ResponseWriter.Header().Set("Content-Length", types.String(stat.Size()))
|
||||
this.ResponseWriter.Header().Set("Content-Type", mimeType)
|
||||
|
||||
fp, err := os.Open(path)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = fp.Close()
|
||||
}()
|
||||
_, err = io.Copy(this.ResponseWriter, fp)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (this *IndexAction) readMenu(docRoot string) {
|
||||
var path = filepath.Clean(docRoot + "/@toc.md")
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var markdown = goldmark.New()
|
||||
|
||||
var buf = &bytes.Buffer{}
|
||||
err = markdown.Convert(data, buf)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var resultString = buf.String()
|
||||
var reg = regexp.MustCompile(`(?U)<a href="(.+)">`)
|
||||
resultString = reg.ReplaceAllStringFunc(resultString, func(s string) string {
|
||||
var match = reg.FindStringSubmatch(s)
|
||||
if len(match) > 1 {
|
||||
var link = match[1]
|
||||
var isExternal = strings.HasPrefix(link, "http://") || strings.HasPrefix(link, "https://")
|
||||
if !isExternal && !strings.HasPrefix(link, "/") {
|
||||
link = URLPrefix + "/" + link
|
||||
}
|
||||
if !isExternal && strings.HasSuffix(link, ".md") {
|
||||
link = strings.TrimSuffix(link, ".md") + ".html"
|
||||
}
|
||||
var a = `<a href="` + link + `"`
|
||||
|
||||
// open external url in new window
|
||||
if isExternal {
|
||||
a += ` target="_blank"`
|
||||
}
|
||||
|
||||
// active
|
||||
if link == this.Request.URL.Path {
|
||||
a += ` class="active"`
|
||||
}
|
||||
|
||||
return a + ">"
|
||||
}
|
||||
return s
|
||||
})
|
||||
|
||||
this.Data["rootTOC"] = resultString
|
||||
}
|
||||
|
||||
type TOCItem struct {
|
||||
Depth int `json:"depth"`
|
||||
Id string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Children []*TOCItem `json:"children"`
|
||||
Parent *TOCItem `json:"-"`
|
||||
}
|
||||
|
||||
func (this *TOCItem) UnsetParent() {
|
||||
this.Parent = nil
|
||||
for _, child := range this.Children {
|
||||
child.UnsetParent()
|
||||
}
|
||||
}
|
||||
|
||||
func (this *TOCItem) AsHTML() string {
|
||||
if len(this.Children) == 0 || this.Depth >= 2 /** only 2 levels **/ {
|
||||
return "\n"
|
||||
}
|
||||
|
||||
var space = strings.Repeat(" ", this.Depth)
|
||||
var result = space + "<ul>\n"
|
||||
|
||||
for _, child := range this.Children {
|
||||
var childSpace = strings.Repeat(" ", child.Depth)
|
||||
result += childSpace + "<li><a href=\"#" + child.Id + "\">" + child.Title + "</a>\n" + child.AsHTML() + childSpace + "</li>\n"
|
||||
}
|
||||
|
||||
result += space + "</ul>\n"
|
||||
|
||||
return result
|
||||
}
|
||||
21
EdgeUser/internal/web/actions/default/docs/init.go
Normal file
21
EdgeUser/internal/web/actions/default/docs/init.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package docs
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Prefix("/docs").
|
||||
Helper(helpers.NewUserMustAuth("")).
|
||||
Data("teaMenu", "docs").
|
||||
Get("", new(IndexAction)).
|
||||
Get("/", new(IndexAction)).
|
||||
Get("/:page(.+)", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
12
EdgeUser/internal/web/actions/default/email/init.go
Normal file
12
EdgeUser/internal/web/actions/default/email/init.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package email
|
||||
|
||||
import "github.com/iwind/TeaGo"
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Prefix("/email").
|
||||
Get("/verify/:code", new(VerifyAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
66
EdgeUser/internal/web/actions/default/email/verify.go
Normal file
66
EdgeUser/internal/web/actions/default/email/verify.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package email
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/configloaders"
|
||||
teaconst "github.com/TeaOSLab/EdgeUser/internal/const"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
type VerifyAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *VerifyAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *VerifyAction) RunGet(params struct {
|
||||
Code string
|
||||
}) {
|
||||
if len(params.Code) == 0 || len(params.Code) > 128 /** 最长不超过128 **/ {
|
||||
this.WriteString("code not found")
|
||||
return
|
||||
}
|
||||
|
||||
// 激活
|
||||
resp, err := this.RPC().UserEmailVerificationRPC().VerifyUserEmail(this.UserContext(), &pb.VerifyUserEmailRequest{
|
||||
Code: params.Code,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["email"] = resp.Email
|
||||
|
||||
if resp.UserId > 0 {
|
||||
this.Data["isOk"] = true
|
||||
|
||||
if this.UserId() > 0 && this.UserId() != resp.UserId {
|
||||
// TODO 暂时不对比当前用户是否一致
|
||||
}
|
||||
} else {
|
||||
this.Data["isOk"] = false
|
||||
this.Data["errorMessage"] = resp.ErrorMessage
|
||||
}
|
||||
|
||||
// 界面
|
||||
config, err := configloaders.LoadUIConfig()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["systemName"] = config.UserSystemName
|
||||
this.Data["showVersion"] = config.ShowVersion
|
||||
if len(config.Version) > 0 {
|
||||
this.Data["version"] = config.Version
|
||||
} else {
|
||||
this.Data["version"] = teaconst.Version
|
||||
}
|
||||
this.Data["faviconFileId"] = config.FaviconFileId
|
||||
|
||||
this.Show()
|
||||
}
|
||||
62
EdgeUser/internal/web/actions/default/files/file.go
Normal file
62
EdgeUser/internal/web/actions/default/files/file.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package files
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type FileAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *FileAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *FileAction) RunGet(params struct {
|
||||
FileId int64
|
||||
}) {
|
||||
fileResp, err := this.RPC().FileRPC().FindEnabledFile(this.UserContext(), &pb.FindEnabledFileRequest{FileId: params.FileId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var file = fileResp.File
|
||||
if file == nil {
|
||||
this.NotFound("File", params.FileId)
|
||||
return
|
||||
}
|
||||
|
||||
chunkIdsResp, err := this.RPC().FileChunkRPC().FindAllFileChunkIds(this.UserContext(), &pb.FindAllFileChunkIdsRequest{FileId: file.Id})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.AddHeader("Content-Length", types.String(file.Size))
|
||||
if len(file.MimeType) > 0 {
|
||||
this.AddHeader("Content-Type", file.MimeType)
|
||||
} else if len(file.Filename) > 0 {
|
||||
var ext = filepath.Ext(file.Filename)
|
||||
var mimeType = mime.TypeByExtension(ext)
|
||||
this.AddHeader("Content-Type", mimeType)
|
||||
}
|
||||
|
||||
for _, chunkId := range chunkIdsResp.FileChunkIds {
|
||||
chunkResp, err := this.RPC().FileChunkRPC().DownloadFileChunk(this.UserContext(), &pb.DownloadFileChunkRequest{FileChunkId: chunkId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if chunkResp.FileChunk == nil {
|
||||
continue
|
||||
}
|
||||
_, _ = this.Write(chunkResp.FileChunk.Data)
|
||||
}
|
||||
}
|
||||
14
EdgeUser/internal/web/actions/default/files/init.go
Normal file
14
EdgeUser/internal/web/actions/default/files/init.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package files
|
||||
|
||||
import "github.com/iwind/TeaGo"
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Prefix("/files").
|
||||
Get("/file", new(FileAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
162
EdgeUser/internal/web/actions/default/finance/bills/bill.go
Normal file
162
EdgeUser/internal/web/actions/default/finance/bills/bill.go
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package bills
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils/numberutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
type BillAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *BillAction) Init() {
|
||||
this.Nav("", "", "index")
|
||||
}
|
||||
|
||||
func (this *BillAction) RunGet(params struct {
|
||||
Code string
|
||||
}) {
|
||||
userBillResp, err := this.RPC().UserBillRPC().FindUserBill(this.UserContext(), &pb.FindUserBillRequest{Code: params.Code})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var bill = userBillResp.UserBill
|
||||
if bill == nil {
|
||||
this.NotFound("user bill", 0)
|
||||
return
|
||||
}
|
||||
|
||||
var userMap maps.Map = nil
|
||||
if bill.User != nil {
|
||||
userMap = maps.Map{
|
||||
"id": bill.User.Id,
|
||||
"fullname": bill.User.Fullname,
|
||||
"username": bill.User.Username,
|
||||
}
|
||||
}
|
||||
|
||||
var month = bill.Month
|
||||
var dayFrom = bill.DayFrom
|
||||
var dayTo = bill.DayTo
|
||||
|
||||
this.Data["bill"] = maps.Map{
|
||||
"id": bill.Id,
|
||||
"isPaid": bill.IsPaid,
|
||||
"month": month,
|
||||
"dayFrom": dayFrom,
|
||||
"dayTo": dayTo,
|
||||
"amount": numberutils.FormatFloat(bill.Amount, 2),
|
||||
"typeName": bill.TypeName,
|
||||
"user": userMap,
|
||||
"description": bill.Description,
|
||||
"code": bill.Code,
|
||||
"canPay": bill.CanPay,
|
||||
"pricePeriodName": userconfigs.PricePeriodName(bill.PricePeriod),
|
||||
"pricePeriod": bill.PricePeriod,
|
||||
}
|
||||
|
||||
// 服务账单
|
||||
var serverBillMaps = []maps.Map{}
|
||||
if (bill.Type == "traffic" || bill.Type == "trafficAndBandwidth") && bill.User != nil {
|
||||
countResp, err := this.RPC().ServerBillRPC().CountAllServerBills(this.UserContext(), &pb.CountAllServerBillsRequest{
|
||||
UserId: bill.User.Id,
|
||||
Month: bill.Month,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var count = countResp.Count
|
||||
var page = this.NewPage(count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
serverBillsResp, err := this.RPC().ServerBillRPC().ListServerBills(this.UserContext(), &pb.ListServerBillsRequest{
|
||||
UserId: bill.User.Id,
|
||||
Month: bill.Month,
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
for _, serverBill := range serverBillsResp.ServerBills {
|
||||
// server
|
||||
var serverMap = maps.Map{"id": 0}
|
||||
if serverBill.Server != nil {
|
||||
serverMap = maps.Map{
|
||||
"id": serverBill.Server.Id,
|
||||
"name": serverBill.Server.Name,
|
||||
}
|
||||
}
|
||||
|
||||
// plan
|
||||
var planMap = maps.Map{"id": 0}
|
||||
if serverBill.Plan != nil {
|
||||
planMap = maps.Map{
|
||||
"id": serverBill.Plan.Id,
|
||||
"name": serverBill.Plan.Name,
|
||||
"priceType": serverBill.Plan.PriceType,
|
||||
}
|
||||
}
|
||||
|
||||
serverBillMaps = append(serverBillMaps, maps.Map{
|
||||
"id": serverBill.Id,
|
||||
"server": serverMap,
|
||||
"plan": planMap,
|
||||
"traffic": numberutils.FormatBytes(serverBill.TotalTrafficBytes),
|
||||
"bandwidthPercentile": serverBill.BandwidthPercentile,
|
||||
"bandwidthPercentileSize": numberutils.FormatBytes(serverBill.BandwidthPercentileBytes),
|
||||
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", serverBill.CreatedAt),
|
||||
"amount": numberutils.FormatFloat(serverBill.Amount, 2),
|
||||
"priceType": serverBill.PriceType,
|
||||
})
|
||||
}
|
||||
}
|
||||
this.Data["serverBills"] = serverBillMaps
|
||||
|
||||
// traffic bills
|
||||
var trafficBillMaps = []maps.Map{}
|
||||
trafficBillsResp, err := this.RPC().UserTrafficBillRPC().FindUserTrafficBills(this.UserContext(), &pb.FindUserTrafficBillsRequest{UserBillId: bill.Id})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
for _, trafficBill := range trafficBillsResp.UserTrafficBills {
|
||||
var regionMap = maps.Map{"id": 0}
|
||||
var nodeRegion = trafficBill.NodeRegion
|
||||
if nodeRegion != nil {
|
||||
regionMap = maps.Map{
|
||||
"id": nodeRegion.Id,
|
||||
"name": nodeRegion.Name,
|
||||
}
|
||||
} else if trafficBill.NodeRegionId > 0 {
|
||||
regionMap = maps.Map{
|
||||
"id": trafficBill.NodeRegionId,
|
||||
"name": "[已取消]",
|
||||
}
|
||||
}
|
||||
|
||||
trafficBillMaps = append(trafficBillMaps, maps.Map{
|
||||
"priceType": trafficBill.PriceType,
|
||||
"priceTypeName": userconfigs.PriceTypeName(trafficBill.PriceType),
|
||||
"trafficGB": numberutils.FormatBytes(int64(trafficBill.TrafficGB * (1 << 30))),
|
||||
"trafficPackageGB": numberutils.FormatBytes(int64(trafficBill.TrafficPackageGB * (1 << 30))),
|
||||
"bandwidthMB": numberutils.FormatBits(int64(trafficBill.BandwidthMB * (1 << 20))),
|
||||
"bandwidthPercentile": trafficBill.BandwidthPercentile,
|
||||
"amount": numberutils.FormatFloat(trafficBill.Amount, 2),
|
||||
"pricePerUnit": trafficBill.PricePerUnit,
|
||||
"region": regionMap,
|
||||
})
|
||||
}
|
||||
this.Data["trafficBills"] = trafficBillMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
115
EdgeUser/internal/web/actions/default/finance/bills/index.go
Normal file
115
EdgeUser/internal/web/actions/default/finance/bills/index.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package bills
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils/numberutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "index")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
PaidFlag int32 `default:"-1"`
|
||||
Month string
|
||||
}) {
|
||||
this.Data["paidFlag"] = params.PaidFlag
|
||||
|
||||
// 账号余额
|
||||
accountResp, err := this.RPC().UserAccountRPC().FindEnabledUserAccountWithUserId(this.UserContext(), &pb.FindEnabledUserAccountWithUserIdRequest{
|
||||
UserId: this.UserId(),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var account = accountResp.UserAccount
|
||||
if account == nil {
|
||||
this.Fail("当前用户还没有账号")
|
||||
}
|
||||
|
||||
// 需要支付的总额
|
||||
unpaidAmountResp, err := this.RPC().UserBillRPC().SumUserUnpaidBills(this.UserContext(), &pb.SumUserUnpaidBillsRequest{UserId: this.UserId()})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["accountTotal"] = account.Total
|
||||
this.Data["unpaidAmount"] = unpaidAmountResp.Amount
|
||||
|
||||
countResp, err := this.RPC().UserBillRPC().CountAllUserBills(this.UserContext(), &pb.CountAllUserBillsRequest{
|
||||
PaidFlag: params.PaidFlag,
|
||||
UserId: this.UserId(),
|
||||
Month: params.Month,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
page := this.NewPage(countResp.Count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
billsResp, err := this.RPC().UserBillRPC().ListUserBills(this.UserContext(), &pb.ListUserBillsRequest{
|
||||
PaidFlag: params.PaidFlag,
|
||||
UserId: this.UserId(),
|
||||
Month: params.Month,
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var billMaps = []maps.Map{}
|
||||
for _, bill := range billsResp.UserBills {
|
||||
var userMap maps.Map = nil
|
||||
if bill.User != nil {
|
||||
userMap = maps.Map{
|
||||
"id": bill.User.Id,
|
||||
"fullname": bill.User.Fullname,
|
||||
}
|
||||
}
|
||||
|
||||
var month = bill.Month
|
||||
var dayFrom = bill.DayFrom
|
||||
var dayTo = bill.DayTo
|
||||
|
||||
if len(month) == 6 {
|
||||
month = month[:4] + "-" + month[4:]
|
||||
}
|
||||
if len(dayFrom) == 8 {
|
||||
dayFrom = dayFrom[:4] + "-" + dayFrom[4:6] + "-" + dayFrom[6:]
|
||||
}
|
||||
if len(dayTo) == 8 {
|
||||
dayTo = dayTo[:4] + "-" + dayTo[4:6] + "-" + dayTo[6:]
|
||||
}
|
||||
|
||||
billMaps = append(billMaps, maps.Map{
|
||||
"id": bill.Id,
|
||||
"code": bill.Code,
|
||||
"isPaid": bill.IsPaid,
|
||||
"month": month,
|
||||
"dayFrom": dayFrom,
|
||||
"dayTo": dayTo,
|
||||
"amount": numberutils.FormatFloat(bill.Amount, 2),
|
||||
"typeName": bill.TypeName,
|
||||
"user": userMap,
|
||||
"description": bill.Description,
|
||||
"canPay": bill.CanPay,
|
||||
"pricePeriodName": userconfigs.PricePeriodName(bill.PricePeriod),
|
||||
"pricePeriod": bill.PricePeriod,
|
||||
"isOverdue": bill.IsOverdue,
|
||||
})
|
||||
}
|
||||
this.Data["bills"] = billMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
20
EdgeUser/internal/web/actions/default/finance/bills/init.go
Normal file
20
EdgeUser/internal/web/actions/default/finance/bills/init.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package bills
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth("")).
|
||||
Data("teaMenu", "finance").
|
||||
Data("teaSubMenu", "bills").
|
||||
Prefix("/finance/bills").
|
||||
Get("", new(IndexAction)).
|
||||
Post("/pay", new(PayAction)).
|
||||
Get("/bill", new(BillAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
58
EdgeUser/internal/web/actions/default/finance/bills/pay.go
Normal file
58
EdgeUser/internal/web/actions/default/finance/bills/pay.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package bills
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
)
|
||||
|
||||
type PayAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *PayAction) RunPost(params struct {
|
||||
BillId int64
|
||||
}) {
|
||||
defer this.CreateLogInfo(codes.UserBill_LogPayUserBill, params.BillId)
|
||||
|
||||
billResp, err := this.RPC().UserBillRPC().FindUserBill(this.UserContext(), &pb.FindUserBillRequest{UserBillId: params.BillId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var bill = billResp.UserBill
|
||||
if bill == nil {
|
||||
this.Fail("找不到要支付的账单")
|
||||
}
|
||||
|
||||
if bill.IsPaid {
|
||||
this.Fail("此账单已支付,不需要重复支付")
|
||||
}
|
||||
|
||||
// 账号余额
|
||||
accountResp, err := this.RPC().UserAccountRPC().FindEnabledUserAccountWithUserId(this.UserContext(), &pb.FindEnabledUserAccountWithUserIdRequest{UserId: this.UserId()})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var account = accountResp.UserAccount
|
||||
if account == nil {
|
||||
this.Fail("当前用户还没有账号")
|
||||
}
|
||||
|
||||
if account.Total < bill.Amount {
|
||||
this.Fail("账号余额不足,当前余额:" + types.String(account.Total) + ",需要支付:" + types.String(bill.Amount))
|
||||
}
|
||||
|
||||
// 支付
|
||||
_, err = this.RPC().UserBillRPC().PayUserBill(this.UserContext(), &pb.PayUserBillRequest{UserBillId: params.BillId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
127
EdgeUser/internal/web/actions/default/finance/charge/index.go
Normal file
127
EdgeUser/internal/web/actions/default/finance/charge/index.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package charge
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
this.Data["currentURL"] = url.QueryEscape(this.Request.URL.String())
|
||||
|
||||
// 配置
|
||||
configResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.UserContext(), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeUserOrderConfig})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var config = userconfigs.DefaultUserOrderConfig()
|
||||
if len(configResp.ValueJSON) > 0 {
|
||||
err = json.Unmarshal(configResp.ValueJSON, config)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
this.Data["config"] = config
|
||||
|
||||
// methods
|
||||
methodsResp, err := this.RPC().OrderMethodRPC().FindAllAvailableOrderMethods(this.UserContext(), &pb.FindAllAvailableOrderMethodsRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var methods = methodsResp.OrderMethods
|
||||
var methodMaps = []maps.Map{}
|
||||
if len(methods) == 0 {
|
||||
config.EnablePay = false
|
||||
} else {
|
||||
for _, method := range methods {
|
||||
methodMaps = append(methodMaps, maps.Map{
|
||||
"id": method.Id,
|
||||
"name": method.Name,
|
||||
"code": method.Code,
|
||||
"description": method.Description,
|
||||
})
|
||||
}
|
||||
}
|
||||
this.Data["methods"] = methodMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunPost(params struct {
|
||||
Amount string
|
||||
MethodCode string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
// 检查充值金额
|
||||
if len(params.Amount) == 0 {
|
||||
this.FailField("amount", "请输入充值金额")
|
||||
}
|
||||
|
||||
if len(params.Amount) > 10 {
|
||||
this.FailField("amount", "充值金额不能大于10位")
|
||||
}
|
||||
|
||||
var digitReg = regexp.MustCompile(`^\d+$`)
|
||||
var decimalReg = regexp.MustCompile(`^\d+\.\d+$`)
|
||||
|
||||
if !digitReg.MatchString(params.Amount) && !decimalReg.MatchString(params.Amount) {
|
||||
this.FailField("amount", "充值金额需要是一个数字")
|
||||
}
|
||||
|
||||
if decimalReg.MatchString(params.Amount) {
|
||||
var dotIndex = strings.LastIndex(params.Amount, ".")
|
||||
var decimalString = params.Amount[dotIndex+1:]
|
||||
if len(decimalString) > 2 {
|
||||
this.FailField("amount", "充值金额最多只支持两位小数")
|
||||
}
|
||||
}
|
||||
|
||||
var amount = types.Float64(params.Amount)
|
||||
if amount <= 0 {
|
||||
this.FailField("amount", "充值金额不能为0")
|
||||
}
|
||||
|
||||
// 支付方式
|
||||
if len(params.MethodCode) == 0 {
|
||||
this.Fail("请选择支付方式")
|
||||
}
|
||||
|
||||
// 生成订单
|
||||
createResp, err := this.RPC().UserOrderRPC().CreateUserOrder(this.UserContext(), &pb.CreateUserOrderRequest{
|
||||
Type: userconfigs.OrderTypeCharge,
|
||||
OrderMethodCode: params.MethodCode,
|
||||
Amount: amount,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["code"] = createResp.Code
|
||||
this.Data["payURL"] = createResp.PayURL
|
||||
|
||||
this.Success()
|
||||
}
|
||||
20
EdgeUser/internal/web/actions/default/finance/charge/init.go
Normal file
20
EdgeUser/internal/web/actions/default/finance/charge/init.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package charge
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth("")).
|
||||
Data("teaMenu", "finance").
|
||||
Data("teaSubMenu", "charge").
|
||||
|
||||
// 财务管理
|
||||
Prefix("/finance/charge").
|
||||
GetPost("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package financeutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/rpc"
|
||||
)
|
||||
|
||||
// FindUserBalance 读取用户余额
|
||||
func FindUserBalance(ctx context.Context) (total float64, err error) {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
accountResp, err := rpcClient.UserAccountRPC().FindEnabledUserAccountWithUserId(ctx, &pb.FindEnabledUserAccountWithUserIdRequest{})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if accountResp.UserAccount != nil {
|
||||
return accountResp.UserAccount.Total, nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
64
EdgeUser/internal/web/actions/default/finance/index.go
Normal file
64
EdgeUser/internal/web/actions/default/finance/index.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package bills
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"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
|
||||
}) {
|
||||
this.Data["keyword"] = params.Keyword
|
||||
|
||||
accountResp, err := this.RPC().UserAccountRPC().FindEnabledUserAccountWithUserId(this.UserContext(), &pb.FindEnabledUserAccountWithUserIdRequest{
|
||||
UserId: this.UserId(),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var account = accountResp.UserAccount
|
||||
|
||||
var accountMap = maps.Map{}
|
||||
accountMap["total"] = account.Total
|
||||
accountMap["totalFrozen"] = account.TotalFrozen
|
||||
this.Data["account"] = accountMap
|
||||
|
||||
// 最近操作记录
|
||||
logsResp, err := this.RPC().UserAccountLogRPC().ListUserAccountLogs(this.UserContext(), &pb.ListUserAccountLogsRequest{
|
||||
UserAccountId: account.Id,
|
||||
Offset: 0,
|
||||
Size: 10,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var logMaps = []maps.Map{}
|
||||
for _, log := range logsResp.UserAccountLogs {
|
||||
logMaps = append(logMaps, maps.Map{
|
||||
"id": log.Id,
|
||||
"description": log.Description,
|
||||
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", log.CreatedAt),
|
||||
"delta": log.Delta,
|
||||
"deltaFrozen": log.DeltaFrozen,
|
||||
"total": log.Total,
|
||||
"totalFrozen": log.TotalFrozen,
|
||||
"event": userconfigs.FindAccountEvent(log.EventType),
|
||||
})
|
||||
}
|
||||
this.Data["logs"] = logMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
22
EdgeUser/internal/web/actions/default/finance/init.go
Normal file
22
EdgeUser/internal/web/actions/default/finance/init.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package bills
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth("")).
|
||||
Data("teaMenu", "finance").
|
||||
|
||||
// 财务管理
|
||||
Prefix("/finance").
|
||||
Get("", new(IndexAction)).
|
||||
Post("/methodOptions", new(MethodOptionsAction)).
|
||||
|
||||
//
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
84
EdgeUser/internal/web/actions/default/finance/logs/index.go
Normal file
84
EdgeUser/internal/web/actions/default/finance/logs/index.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils/numberutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
Keyword string
|
||||
EventType string
|
||||
}) {
|
||||
this.Data["keyword"] = params.Keyword
|
||||
this.Data["eventType"] = params.EventType
|
||||
|
||||
// 所有事件类型
|
||||
this.Data["events"] = userconfigs.FindAllAccountEventTypes()
|
||||
|
||||
// 数量
|
||||
countResp, err := this.RPC().UserAccountLogRPC().CountUserAccountLogs(this.UserContext(), &pb.CountUserAccountLogsRequest{
|
||||
Keyword: params.Keyword,
|
||||
EventType: params.EventType,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var count = countResp.Count
|
||||
var page = this.NewPage(count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
logsResp, err := this.RPC().UserAccountLogRPC().ListUserAccountLogs(this.UserContext(), &pb.ListUserAccountLogsRequest{
|
||||
Keyword: params.Keyword,
|
||||
EventType: params.EventType,
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var logMaps = []maps.Map{}
|
||||
for _, log := range logsResp.UserAccountLogs {
|
||||
var userMap = maps.Map{}
|
||||
if log.User != nil {
|
||||
userMap = maps.Map{
|
||||
"id": log.User.Id,
|
||||
"username": log.User.Username,
|
||||
"fullname": log.User.Fullname,
|
||||
}
|
||||
}
|
||||
|
||||
logMaps = append(logMaps, maps.Map{
|
||||
"id": log.Id,
|
||||
"description": log.Description,
|
||||
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", log.CreatedAt),
|
||||
"delta": log.Delta,
|
||||
"deltaFormatted": numberutils.FormatFloat(log.Delta, 2),
|
||||
"deltaFrozen": log.DeltaFrozen,
|
||||
"total": log.Total,
|
||||
"totalFormatted": numberutils.FormatFloat(log.Total, 2),
|
||||
"totalFrozen": log.TotalFrozen,
|
||||
"event": userconfigs.FindAccountEvent(log.EventType),
|
||||
"user": userMap,
|
||||
"accountId": log.UserAccountId,
|
||||
})
|
||||
}
|
||||
this.Data["logs"] = logMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
20
EdgeUser/internal/web/actions/default/finance/logs/init.go
Normal file
20
EdgeUser/internal/web/actions/default/finance/logs/init.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth("")).
|
||||
Data("teaMenu", "finance").
|
||||
Data("teaSubMenu", "logs").
|
||||
|
||||
// 财务管理
|
||||
Prefix("/finance/logs").
|
||||
Get("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package bills
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/finance/financeutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type MethodOptionsAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *MethodOptionsAction) RunPost(params struct{}) {
|
||||
// 配置
|
||||
configResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.UserContext(), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeUserOrderConfig})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var config = userconfigs.DefaultUserOrderConfig()
|
||||
if len(configResp.ValueJSON) > 0 {
|
||||
err = json.Unmarshal(configResp.ValueJSON, config)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
this.Data["config"] = config
|
||||
|
||||
// methods
|
||||
methodsResp, err := this.RPC().OrderMethodRPC().FindAllAvailableOrderMethods(this.UserContext(), &pb.FindAllAvailableOrderMethodsRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var methods = methodsResp.OrderMethods
|
||||
|
||||
// 没有可用的支付方式时也不影响支付
|
||||
var methodMaps = []maps.Map{}
|
||||
for _, method := range methods {
|
||||
methodMaps = append(methodMaps, maps.Map{
|
||||
"id": method.Id,
|
||||
"name": method.Name,
|
||||
"code": method.Code,
|
||||
"description": method.Description,
|
||||
})
|
||||
}
|
||||
|
||||
this.Data["enablePay"] = config.EnablePay
|
||||
this.Data["methods"] = methodMaps
|
||||
|
||||
// 用户余额
|
||||
balance, err := financeutils.FindUserBalance(this.UserContext())
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["balance"] = balance
|
||||
|
||||
this.Success()
|
||||
}
|
||||
94
EdgeUser/internal/web/actions/default/finance/pay/index.go
Normal file
94
EdgeUser/internal/web/actions/default/finance/pay/index.go
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package pay
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/ttlcache"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"github.com/skip2/go-qrcode"
|
||||
"time"
|
||||
)
|
||||
|
||||
var qrcodeKeySalt = rands.HexString(32)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
Code string
|
||||
From string
|
||||
ReturnURL string
|
||||
}) {
|
||||
this.Data["fromURL"] = params.From
|
||||
this.Data["returnURL"] = params.ReturnURL
|
||||
|
||||
orderResp, err := this.RPC().UserOrderRPC().FindEnabledUserOrder(this.UserContext(), &pb.FindEnabledUserOrderRequest{Code: params.Code})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var order = orderResp.UserOrder
|
||||
if order == nil {
|
||||
this.ErrorPage(errors.New("can not find order with code '" + params.Code + "'"))
|
||||
return
|
||||
}
|
||||
|
||||
var methodMap = maps.Map{
|
||||
"name": "",
|
||||
}
|
||||
var qrcodeKey = ""
|
||||
var qrcodeTitle = ""
|
||||
if order.OrderMethod != nil {
|
||||
methodMap = maps.Map{
|
||||
"name": order.OrderMethod.Name,
|
||||
}
|
||||
|
||||
qrcodeTitle = order.OrderMethod.QrcodeTitle
|
||||
|
||||
if len(qrcodeTitle) == 0 && len(order.OrderMethod.ParentCode) > 0 {
|
||||
var parentDef = userconfigs.FindPresetPayMethodWithCode(order.OrderMethod.ParentCode)
|
||||
if parentDef != nil {
|
||||
qrcodeTitle = parentDef.QRCodeTitle
|
||||
}
|
||||
}
|
||||
|
||||
if order.OrderMethod.ClientType == userconfigs.PayClientTypeMobile {
|
||||
data, err := qrcode.Encode(order.PayURL, qrcode.Medium, 256)
|
||||
if err != nil {
|
||||
this.ErrorPage(errors.New("二维码生成失败:" + err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
qrcodeKey = stringutil.Md5(qrcodeKeySalt + ":order:" + order.Code)
|
||||
ttlcache.DefaultCache.Write(qrcodeKey, data, time.Now().Unix()+600 /** 只保留10分钟,防止内存占用过高 **/)
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["order"] = maps.Map{
|
||||
"code": order.Code,
|
||||
"amount": order.Amount,
|
||||
"canPay": order.CanPay,
|
||||
"payURL": order.PayURL,
|
||||
"typeName": userconfigs.FindOrderTypeName(order.Type),
|
||||
"method": methodMap,
|
||||
"isExpired": order.IsExpired,
|
||||
"status": order.Status,
|
||||
"statusName": userconfigs.FindOrderStatusName(order.Status),
|
||||
"urlQRCodeKey": qrcodeKey,
|
||||
"qrcodeTitle": qrcodeTitle,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
18
EdgeUser/internal/web/actions/default/finance/pay/init.go
Normal file
18
EdgeUser/internal/web/actions/default/finance/pay/init.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package pay
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth("")).
|
||||
Data("teaMenu", "finance").
|
||||
Data("teaSubMenu", "orders").
|
||||
Prefix("/finance/pay").
|
||||
Get("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
30
EdgeUser/internal/web/actions/default/index/checkOTP.go
Normal file
30
EdgeUser/internal/web/actions/default/index/checkOTP.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
// CheckOTPAction 检查是否需要OTP
|
||||
type CheckOTPAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *CheckOTPAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *CheckOTPAction) RunPost(params struct {
|
||||
Username string
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
checkResp, err := this.RPC().UserRPC().CheckUserOTPWithUsername(this.UserContext(), &pb.CheckUserOTPWithUsernameRequest{Username: params.Username})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["requireOTP"] = checkResp.RequireOTP
|
||||
this.Success()
|
||||
}
|
||||
91
EdgeUser/internal/web/actions/default/index/index.go
Normal file
91
EdgeUser/internal/web/actions/default/index/index.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/configloaders"
|
||||
teaconst "github.com/TeaOSLab/EdgeUser/internal/const"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils/portalutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/login/loginutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.LoginAction
|
||||
}
|
||||
|
||||
// 首页(登录页)
|
||||
// 修改此页面时需要同步修改 login/index.go
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
From string
|
||||
|
||||
Auth *helpers.UserShouldAuth
|
||||
}) {
|
||||
// 跳转到自定义页面
|
||||
if portalutils.HasPortalIndex() {
|
||||
portalutils.ReadPortalIndex(this.ResponseWriter)
|
||||
return
|
||||
}
|
||||
|
||||
// 加载UI配置
|
||||
config, err := configloaders.LoadUIConfig()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 检测是否自动跳转到HTTPS
|
||||
this.CheckHTTPSRedirecting()
|
||||
|
||||
// 跳转到Portal
|
||||
if config.Portal.IsOn {
|
||||
this.RedirectURL("/portal")
|
||||
return
|
||||
}
|
||||
|
||||
// 已登录跳转到dashboard
|
||||
if params.Auth.IsUser() {
|
||||
this.RedirectURL("/dashboard")
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["isUser"] = false
|
||||
this.Data["menu"] = "signIn"
|
||||
|
||||
var timestamp = fmt.Sprintf("%d", time.Now().Unix())
|
||||
this.Data["token"] = stringutil.Md5(actionutils.TokenSalt+timestamp) + timestamp
|
||||
this.Data["from"] = params.From
|
||||
|
||||
this.Data["systemName"] = config.UserSystemName
|
||||
this.Data["showVersion"] = config.ShowVersion
|
||||
if len(config.Version) > 0 {
|
||||
this.Data["version"] = config.Version
|
||||
} else {
|
||||
this.Data["version"] = teaconst.Version
|
||||
}
|
||||
this.Data["faviconFileId"] = config.FaviconFileId
|
||||
this.Data["logoFileId"] = config.LogoFileId
|
||||
this.Data["themeBackgroundColor"] = config.Theme.BackgroundColor
|
||||
|
||||
// 是否可以注册
|
||||
this.Data["canRegister"] = false
|
||||
this.Data["emailCanLogin"] = false
|
||||
this.Data["canResetPassword"] = false
|
||||
{
|
||||
registerConfig, _ := configloaders.LoadRegisterConfig()
|
||||
if registerConfig != nil {
|
||||
this.Data["canRegister"] = registerConfig.IsOn
|
||||
this.Data["emailCanLogin"] = registerConfig.EmailVerification.IsOn && registerConfig.EmailVerification.CanLogin
|
||||
this.Data["canResetPassword"] = registerConfig.EmailVerification.IsOn && registerConfig.EmailResetPassword.IsOn
|
||||
this.Data["mobileCanLogin"] = registerConfig.MobileVerification.IsOn && registerConfig.MobileVerification.CanLogin
|
||||
}
|
||||
}
|
||||
|
||||
// 删除Cookie
|
||||
loginutils.UnsetCookie(this.Object())
|
||||
|
||||
this.Show()
|
||||
}
|
||||
14
EdgeUser/internal/web/actions/default/index/init.go
Normal file
14
EdgeUser/internal/web/actions/default/index/init.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.Prefix("").
|
||||
GetPost("/", new(IndexAction)).
|
||||
Post("/checkOTP", new(CheckOTPAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
547
EdgeUser/internal/web/actions/default/lb/create.go
Normal file
547
EdgeUser/internal/web/actions/default/lb/create.go
Normal file
@@ -0,0 +1,547 @@
|
||||
package lb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type CreateAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *CreateAction) Init() {
|
||||
this.Nav("", "", "create")
|
||||
}
|
||||
|
||||
func (this *CreateAction) RunGet(params struct{}) {
|
||||
var supportTCP = this.ValidateFeature(userconfigs.UserFeatureCodeServerTCP, 0)
|
||||
var supportUDP = this.ValidateFeature(userconfigs.UserFeatureCodeServerUDP, 0)
|
||||
|
||||
if !supportTCP && !supportUDP {
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["supportTCP"] = supportTCP
|
||||
this.Data["supportUDP"] = supportUDP
|
||||
|
||||
// 服务类型
|
||||
var serverTypes = []maps.Map{}
|
||||
if supportTCP {
|
||||
serverTypes = append(serverTypes, maps.Map{
|
||||
"name": "TCP负载均衡",
|
||||
"code": serverconfigs.ServerTypeTCPProxy,
|
||||
})
|
||||
}
|
||||
if supportUDP {
|
||||
serverTypes = append(serverTypes, maps.Map{
|
||||
"name": "UDP负载均衡",
|
||||
"code": serverconfigs.ServerTypeUDPProxy,
|
||||
})
|
||||
}
|
||||
this.Data["serverTypes"] = serverTypes
|
||||
|
||||
this.Data["canSpecifyTCPPort"] = this.ValidateFeature(userconfigs.UserFeatureCodeServerTCPPort, 0)
|
||||
this.Data["canSpecifyUDPPort"] = this.ValidateFeature(userconfigs.UserFeatureCodeServerUDPPort, 0)
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *CreateAction) RunPost(params struct {
|
||||
Name string
|
||||
ServerType string
|
||||
Protocols []string
|
||||
CertIdsJSON []byte
|
||||
OriginsJSON []byte
|
||||
TcpPorts []int
|
||||
TlsPorts []int
|
||||
UdpPorts []int
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
// 检查ServerType
|
||||
var serverType = params.ServerType
|
||||
if !lists.ContainsString([]string{serverconfigs.ServerTypeTCPProxy, serverconfigs.ServerTypeUDPProxy}, serverType) {
|
||||
this.Fail("请选择正确的服务类型")
|
||||
}
|
||||
|
||||
// 检查用户所在集群
|
||||
clusterIdResp, err := this.RPC().UserRPC().FindUserNodeClusterId(this.UserContext(), &pb.FindUserNodeClusterIdRequest{UserId: this.UserId()})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
clusterId := clusterIdResp.NodeClusterId
|
||||
if clusterId == 0 {
|
||||
this.Fail("当前用户没有指定集群,不能使用此服务")
|
||||
}
|
||||
|
||||
// 检查是否有TCP权限
|
||||
if lists.ContainsString(params.Protocols, "tcp") && !this.ValidateFeature(userconfigs.UserFeatureCodeServerTCP, 0) {
|
||||
this.Fail("你没有权限使用TCP负载均衡功能")
|
||||
}
|
||||
|
||||
// 检查是否有UDP权限
|
||||
if lists.ContainsString(params.Protocols, "udp") && !this.ValidateFeature(userconfigs.UserFeatureCodeServerUDP, 0) {
|
||||
this.Fail("你没有权限使用UDP负载均衡功能")
|
||||
}
|
||||
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入服务名称")
|
||||
|
||||
// 协议
|
||||
if len(params.Protocols) == 0 {
|
||||
this.Fail("请选择至少一个协议")
|
||||
}
|
||||
|
||||
// TCP
|
||||
canSpecifyTCPPort := this.ValidateFeature(userconfigs.UserFeatureCodeServerTCPPort, 0)
|
||||
if serverType == serverconfigs.ServerTypeTCPProxy {
|
||||
// 检查TCP端口
|
||||
if canSpecifyTCPPort {
|
||||
if lists.Contains(params.Protocols, "tcp") {
|
||||
if len(params.TcpPorts) == 0 {
|
||||
this.Fail("需要至少指定一个TCP监听端口")
|
||||
}
|
||||
for _, port := range params.TcpPorts {
|
||||
if port < 1024 || port > 65534 {
|
||||
this.Fail("端口 '" + strconv.Itoa(port) + "' 范围错误")
|
||||
}
|
||||
|
||||
// 检查是否被使用
|
||||
resp, err := this.RPC().NodeClusterRPC().CheckPortIsUsingInNodeCluster(this.UserContext(), &pb.CheckPortIsUsingInNodeClusterRequest{
|
||||
Port: types.Int32(port),
|
||||
NodeClusterId: clusterId,
|
||||
ProtocolFamily: "tcp",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if resp.IsUsing {
|
||||
this.Fail("端口 '" + strconv.Itoa(port) + "' 正在被别的服务使用,请换一个")
|
||||
}
|
||||
}
|
||||
}
|
||||
if lists.Contains(params.Protocols, "tls") {
|
||||
if len(params.TlsPorts) == 0 {
|
||||
this.Fail("需要至少指定一个TLS监听端口")
|
||||
}
|
||||
for _, port := range params.TlsPorts {
|
||||
if port < 1024 || port > 65534 {
|
||||
this.Fail("端口 '" + strconv.Itoa(port) + "' 范围错误")
|
||||
}
|
||||
if lists.ContainsInt(params.TcpPorts, port) {
|
||||
this.Fail("TLS端口 '" + strconv.Itoa(port) + "' 已经被TCP端口使用,不能重复使用")
|
||||
}
|
||||
|
||||
// 检查是否被使用
|
||||
resp, err := this.RPC().NodeClusterRPC().CheckPortIsUsingInNodeCluster(this.UserContext(), &pb.CheckPortIsUsingInNodeClusterRequest{
|
||||
Port: types.Int32(port),
|
||||
NodeClusterId: clusterId,
|
||||
ProtocolFamily: "tcp",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if resp.IsUsing {
|
||||
this.Fail("端口 '" + strconv.Itoa(port) + "' 正在被别的服务使用,请换一个")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UDP
|
||||
canSpecifyUDPPort := this.ValidateFeature(userconfigs.UserFeatureCodeServerUDPPort, 0)
|
||||
if serverType == serverconfigs.ServerTypeUDPProxy {
|
||||
// 检查UDP端口
|
||||
if canSpecifyUDPPort {
|
||||
if lists.Contains(params.Protocols, "udp") {
|
||||
if len(params.UdpPorts) == 0 {
|
||||
this.Fail("需要至少指定一个UDP监听端口")
|
||||
}
|
||||
for _, port := range params.UdpPorts {
|
||||
if port < 1024 || port > 65534 {
|
||||
this.Fail("端口 '" + strconv.Itoa(port) + "' 范围错误")
|
||||
}
|
||||
|
||||
// 检查是否被使用
|
||||
resp, err := this.RPC().NodeClusterRPC().CheckPortIsUsingInNodeCluster(this.UserContext(), &pb.CheckPortIsUsingInNodeClusterRequest{
|
||||
Port: types.Int32(port),
|
||||
NodeClusterId: clusterId,
|
||||
ProtocolFamily: "udp",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if resp.IsUsing {
|
||||
this.Fail("端口 '" + strconv.Itoa(port) + "' 正在被别的服务使用,请换一个")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 先加锁
|
||||
lockerKey := "create_tcp_server"
|
||||
lockResp, err := this.RPC().SysLockerRPC().SysLockerLock(this.UserContext(), &pb.SysLockerLockRequest{
|
||||
Key: lockerKey,
|
||||
TimeoutSeconds: 30,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if !lockResp.Ok {
|
||||
this.Fail("操作繁忙,请稍后再试")
|
||||
}
|
||||
defer func() {
|
||||
_, err := this.RPC().SysLockerRPC().SysLockerUnlock(this.UserContext(), &pb.SysLockerUnlockRequest{Key: lockerKey})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
tcpConfig := &serverconfigs.TCPProtocolConfig{}
|
||||
tlsConfig := &serverconfigs.TLSProtocolConfig{}
|
||||
udpConfig := &serverconfigs.UDPProtocolConfig{}
|
||||
|
||||
if serverType == serverconfigs.ServerTypeTCPProxy {
|
||||
// TCP
|
||||
ports := []int{}
|
||||
if lists.ContainsString(params.Protocols, "tcp") {
|
||||
tcpConfig.IsOn = true
|
||||
|
||||
if canSpecifyTCPPort {
|
||||
for _, port := range params.TcpPorts {
|
||||
tcpConfig.Listen = append(tcpConfig.Listen, &serverconfigs.NetworkAddressConfig{
|
||||
Protocol: serverconfigs.ProtocolTCP,
|
||||
Host: "",
|
||||
PortRange: strconv.Itoa(port),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 获取随机端口
|
||||
portResp, err := this.RPC().NodeClusterRPC().FindFreePortInNodeCluster(this.UserContext(), &pb.FindFreePortInNodeClusterRequest{
|
||||
NodeClusterId: clusterId,
|
||||
ProtocolFamily: "tcp",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
port := int(portResp.Port)
|
||||
ports = append(ports, port)
|
||||
tcpConfig.Listen = []*serverconfigs.NetworkAddressConfig{
|
||||
{
|
||||
Protocol: serverconfigs.ProtocolTCP,
|
||||
Host: "",
|
||||
PortRange: strconv.Itoa(port),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TLS
|
||||
if lists.ContainsString(params.Protocols, "tls") {
|
||||
tlsConfig.IsOn = true
|
||||
|
||||
if canSpecifyTCPPort {
|
||||
for _, port := range params.TlsPorts {
|
||||
tlsConfig.Listen = append(tlsConfig.Listen, &serverconfigs.NetworkAddressConfig{
|
||||
Protocol: serverconfigs.ProtocolTLS,
|
||||
Host: "",
|
||||
PortRange: strconv.Itoa(port),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
var port int
|
||||
|
||||
// 尝试N次
|
||||
for i := 0; i < 5; i++ {
|
||||
// 获取随机端口
|
||||
portResp, err := this.RPC().NodeClusterRPC().FindFreePortInNodeCluster(this.UserContext(), &pb.FindFreePortInNodeClusterRequest{
|
||||
NodeClusterId: clusterId,
|
||||
ProtocolFamily: "tcp",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
p := int(portResp.Port)
|
||||
if !lists.ContainsInt(ports, p) {
|
||||
port = p
|
||||
break
|
||||
}
|
||||
}
|
||||
if port == 0 {
|
||||
this.Fail("无法找到可用的端口,请稍后重试")
|
||||
}
|
||||
tlsConfig.Listen = []*serverconfigs.NetworkAddressConfig{
|
||||
{
|
||||
Protocol: serverconfigs.ProtocolTLS,
|
||||
Host: "",
|
||||
PortRange: strconv.Itoa(port),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if len(params.CertIdsJSON) == 0 {
|
||||
this.Fail("请选择或者上传TLS证书")
|
||||
}
|
||||
certIds := []int64{}
|
||||
err := json.Unmarshal(params.CertIdsJSON, &certIds)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if len(certIds) == 0 {
|
||||
this.Fail("请选择或者上传TLS证书")
|
||||
}
|
||||
|
||||
certRefs := []*sslconfigs.SSLCertRef{}
|
||||
for _, certId := range certIds {
|
||||
certRefs = append(certRefs, &sslconfigs.SSLCertRef{
|
||||
IsOn: true,
|
||||
CertId: certId,
|
||||
})
|
||||
}
|
||||
certRefsJSON, err := json.Marshal(certRefs)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建策略
|
||||
sslPolicyIdResp, err := this.RPC().SSLPolicyRPC().CreateSSLPolicy(this.UserContext(), &pb.CreateSSLPolicyRequest{
|
||||
Http2Enabled: false,
|
||||
Http3Enabled: false,
|
||||
MinVersion: "TLS 1.1",
|
||||
SslCertsJSON: certRefsJSON,
|
||||
HstsJSON: nil,
|
||||
ClientAuthType: 0,
|
||||
ClientCACertsJSON: nil,
|
||||
CipherSuites: nil,
|
||||
CipherSuitesIsOn: false,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
tlsConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
|
||||
IsOn: true,
|
||||
SSLPolicyId: sslPolicyIdResp.SslPolicyId,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UDP
|
||||
if serverType == serverconfigs.ServerTypeUDPProxy {
|
||||
if lists.ContainsString(params.Protocols, "udp") {
|
||||
udpConfig.IsOn = true
|
||||
|
||||
if canSpecifyUDPPort {
|
||||
for _, port := range params.UdpPorts {
|
||||
udpConfig.Listen = append(udpConfig.Listen, &serverconfigs.NetworkAddressConfig{
|
||||
Protocol: serverconfigs.ProtocolUDP,
|
||||
Host: "",
|
||||
PortRange: strconv.Itoa(port),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 获取随机端口
|
||||
portResp, err := this.RPC().NodeClusterRPC().FindFreePortInNodeCluster(this.UserContext(), &pb.FindFreePortInNodeClusterRequest{
|
||||
NodeClusterId: clusterId,
|
||||
ProtocolFamily: "udp",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
port := int(portResp.Port)
|
||||
udpConfig.Listen = []*serverconfigs.NetworkAddressConfig{
|
||||
{
|
||||
Protocol: serverconfigs.ProtocolUDP,
|
||||
Host: "",
|
||||
PortRange: strconv.Itoa(port),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 源站信息
|
||||
originMaps := []maps.Map{}
|
||||
if len(params.OriginsJSON) == 0 {
|
||||
this.Fail("请输入源站信息")
|
||||
}
|
||||
err = json.Unmarshal(params.OriginsJSON, &originMaps)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if len(originMaps) == 0 {
|
||||
this.Fail("请输入源站信息")
|
||||
}
|
||||
primaryOriginRefs := []*serverconfigs.OriginRef{}
|
||||
backupOriginRefs := []*serverconfigs.OriginRef{}
|
||||
for _, originMap := range originMaps {
|
||||
host := originMap.GetString("host")
|
||||
isPrimary := originMap.GetBool("isPrimary")
|
||||
scheme := originMap.GetString("scheme")
|
||||
|
||||
if len(host) == 0 {
|
||||
this.Fail("源站地址不能为空")
|
||||
}
|
||||
addrHost, addrPort, err := net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
this.Fail("源站地址'" + host + "'格式错误")
|
||||
}
|
||||
|
||||
if (serverType == serverconfigs.ServerTypeTCPProxy && scheme != "tcp" && scheme != "tls") ||
|
||||
(serverType == serverconfigs.ServerTypeUDPProxy && scheme != "udp") {
|
||||
this.Fail("错误的源站协议")
|
||||
}
|
||||
|
||||
originIdResp, err := this.RPC().OriginRPC().CreateOrigin(this.UserContext(), &pb.CreateOriginRequest{
|
||||
Name: "",
|
||||
Addr: &pb.NetworkAddress{
|
||||
Protocol: scheme,
|
||||
Host: addrHost,
|
||||
PortRange: addrPort,
|
||||
},
|
||||
Description: "",
|
||||
Weight: 10,
|
||||
IsOn: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if isPrimary {
|
||||
primaryOriginRefs = append(primaryOriginRefs, &serverconfigs.OriginRef{
|
||||
IsOn: true,
|
||||
OriginId: originIdResp.OriginId,
|
||||
})
|
||||
} else {
|
||||
backupOriginRefs = append(backupOriginRefs, &serverconfigs.OriginRef{
|
||||
IsOn: true,
|
||||
OriginId: originIdResp.OriginId,
|
||||
})
|
||||
}
|
||||
}
|
||||
primaryOriginsJSON, err := json.Marshal(primaryOriginRefs)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
backupOriginsJSON, err := json.Marshal(backupOriginRefs)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
scheduling := &serverconfigs.SchedulingConfig{
|
||||
Code: "random",
|
||||
Options: nil,
|
||||
}
|
||||
schedulingJSON, err := json.Marshal(scheduling)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 反向代理
|
||||
reverseProxyResp, err := this.RPC().ReverseProxyRPC().CreateReverseProxy(this.UserContext(), &pb.CreateReverseProxyRequest{
|
||||
SchedulingJSON: schedulingJSON,
|
||||
PrimaryOriginsJSON: primaryOriginsJSON,
|
||||
BackupOriginsJSON: backupOriginsJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
reverseProxyId := reverseProxyResp.ReverseProxyId
|
||||
reverseProxyRef := &serverconfigs.ReverseProxyRef{
|
||||
IsPrior: false,
|
||||
IsOn: true,
|
||||
ReverseProxyId: reverseProxyId,
|
||||
}
|
||||
reverseProxyRefJSON, err := json.Marshal(reverseProxyRef)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 开始保存
|
||||
var tcpJSON []byte
|
||||
var tlsJSON []byte
|
||||
var udpJSON []byte
|
||||
if tcpConfig.IsOn {
|
||||
tcpJSON, err = tcpConfig.AsJSON()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if tlsConfig.IsOn {
|
||||
tlsJSON, err = tlsConfig.AsJSON()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if udpConfig.IsOn {
|
||||
udpJSON, err = udpConfig.AsJSON()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
createResp, err := this.RPC().ServerRPC().CreateServer(this.UserContext(), &pb.CreateServerRequest{
|
||||
UserId: this.UserId(),
|
||||
AdminId: 0,
|
||||
Type: serverType,
|
||||
Name: params.Name,
|
||||
Description: "",
|
||||
ServerNamesJSON: []byte("[]"),
|
||||
HttpJSON: nil,
|
||||
HttpsJSON: nil,
|
||||
TcpJSON: tcpJSON,
|
||||
TlsJSON: tlsJSON,
|
||||
UdpJSON: udpJSON,
|
||||
WebId: 0,
|
||||
ReverseProxyJSON: reverseProxyRefJSON,
|
||||
ServerGroupIds: nil,
|
||||
NodeClusterId: clusterId,
|
||||
IncludeNodesJSON: nil,
|
||||
ExcludeNodesJSON: nil,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
serverId := createResp.ServerId
|
||||
|
||||
defer this.CreateLogInfo(codes.Server_LogCreateServer, serverId)
|
||||
|
||||
this.Success()
|
||||
}
|
||||
25
EdgeUser/internal/web/actions/default/lb/delete.go
Normal file
25
EdgeUser/internal/web/actions/default/lb/delete.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package lb
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
type DeleteAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *DeleteAction) RunPost(params struct {
|
||||
ServerId int64
|
||||
}) {
|
||||
defer this.CreateLogInfo(codes.Server_LogDeleteServer, params.ServerId)
|
||||
|
||||
_, err := this.RPC().ServerRPC().DeleteServer(this.UserContext(), &pb.DeleteServerRequest{ServerId: params.ServerId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
213
EdgeUser/internal/web/actions/default/lb/index.go
Normal file
213
EdgeUser/internal/web/actions/default/lb/index.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package lb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "index")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
// 更新用户可用状态
|
||||
stateResp, err := this.RPC().UserRPC().RenewUserServersState(this.UserContext(), &pb.RenewUserServersStateRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["serversIsEnabled"] = stateResp.IsEnabled
|
||||
|
||||
// 提醒有逾期未支付的账单
|
||||
this.Data["countUnpaidBills"] = 0
|
||||
priceConfig, _ := configloaders.LoadCacheableUserPriceConfig()
|
||||
if priceConfig != nil && priceConfig.IsOn && priceConfig.UnpaidBillPolicy.IsOn && (priceConfig.UnpaidBillPolicy.MinDailyBillDays > 0 || priceConfig.UnpaidBillPolicy.MinMonthlyBillDays > 0) {
|
||||
countResp, err := this.RPC().UserBillRPC().CountAllUserBills(this.UserContext(), &pb.CountAllUserBillsRequest{
|
||||
PaidFlag: 0,
|
||||
UserId: this.UserId(),
|
||||
Month: "",
|
||||
TrafficRelated: true,
|
||||
MinDailyBillDays: priceConfig.UnpaidBillPolicy.MinDailyBillDays,
|
||||
MinMonthlyBillDays: priceConfig.UnpaidBillPolicy.MinMonthlyBillDays,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["countUnpaidBills"] = countResp.Count
|
||||
}
|
||||
|
||||
var supportTCP = this.ValidateFeature(userconfigs.UserFeatureCodeServerTCP, 0)
|
||||
var supportUDP = this.ValidateFeature(userconfigs.UserFeatureCodeServerUDP, 0)
|
||||
if !supportTCP && !supportUDP {
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["supportTCP"] = supportTCP
|
||||
this.Data["supportUDP"] = supportUDP
|
||||
|
||||
var protocols = []string{}
|
||||
if supportTCP {
|
||||
protocols = append(protocols, "tcp")
|
||||
}
|
||||
if supportUDP {
|
||||
protocols = append(protocols, "udp")
|
||||
}
|
||||
|
||||
countResp, err := this.RPC().ServerRPC().CountAllEnabledServersMatch(this.UserContext(), &pb.CountAllEnabledServersMatchRequest{
|
||||
ServerGroupId: 0,
|
||||
Keyword: "",
|
||||
UserId: this.UserId(),
|
||||
ProtocolFamily: strings.Join(protocols, ","),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var count = countResp.Count
|
||||
var page = this.NewPage(count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
serversResp, err := this.RPC().ServerRPC().ListEnabledServersMatch(this.UserContext(), &pb.ListEnabledServersMatchRequest{
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
ServerGroupId: 0,
|
||||
Keyword: "",
|
||||
ProtocolFamily: strings.Join(protocols, ","),
|
||||
UserId: this.UserId(),
|
||||
IgnoreSSLCerts: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var serverMaps = []maps.Map{}
|
||||
for _, server := range serversResp.Servers {
|
||||
// CNAME
|
||||
var cname = ""
|
||||
if server.NodeCluster != nil {
|
||||
clusterId := server.NodeCluster.Id
|
||||
if clusterId > 0 {
|
||||
dnsInfoResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.UserContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: clusterId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if dnsInfoResp.Domain != nil {
|
||||
cname = server.DnsName + "." + dnsInfoResp.Domain.Name + "."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TCP
|
||||
var tcpPorts = []string{}
|
||||
if len(server.TcpJSON) > 0 {
|
||||
config, err := serverconfigs.NewTCPProtocolConfigFromJSON(server.TcpJSON)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if config.IsOn {
|
||||
for _, listen := range config.Listen {
|
||||
tcpPorts = append(tcpPorts, listen.PortRange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TLS
|
||||
var tlsPorts = []string{}
|
||||
if len(server.TlsJSON) > 0 {
|
||||
config, err := serverconfigs.NewTLSProtocolConfigFromJSON(server.TlsJSON)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if config.IsOn {
|
||||
for _, listen := range config.Listen {
|
||||
tlsPorts = append(tlsPorts, listen.PortRange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UDP
|
||||
var udpPorts = []string{}
|
||||
if len(server.UdpJSON) > 0 {
|
||||
config, err := serverconfigs.NewUDPProtocolConfigFromJSON(server.UdpJSON)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if config.IsOn {
|
||||
for _, listen := range config.Listen {
|
||||
udpPorts = append(udpPorts, listen.PortRange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 套餐
|
||||
var userPlanMap = maps.Map{"id": 0}
|
||||
if server.UserPlanId > 0 {
|
||||
userPlanResp, err := this.RPC().UserPlanRPC().FindEnabledUserPlan(this.UserContext(), &pb.FindEnabledUserPlanRequest{
|
||||
UserPlanId: server.UserPlanId,
|
||||
})
|
||||
if err != nil {
|
||||
if !utils.IsNotFound(err) {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
var userPlan = userPlanResp.UserPlan
|
||||
if userPlan != nil && userPlan.Plan != nil {
|
||||
if len(userPlan.Name) == 0 {
|
||||
userPlan.Name = userPlan.Plan.Name
|
||||
}
|
||||
userPlanMap = maps.Map{
|
||||
"id": userPlan.Id,
|
||||
"name": userPlan.Name,
|
||||
"dayTo": userPlan.DayTo,
|
||||
"isExpired": userPlan.DayTo <= timeutil.Format("Y-m-d"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 域名和限流状态
|
||||
var trafficLimitStatus *serverconfigs.TrafficLimitStatus
|
||||
if len(server.Config) > 0 {
|
||||
var serverConfig = &serverconfigs.ServerConfig{}
|
||||
err = json.Unmarshal(server.Config, serverConfig)
|
||||
if err == nil {
|
||||
if serverConfig.TrafficLimitStatus != nil && serverConfig.TrafficLimitStatus.IsValid() {
|
||||
trafficLimitStatus = serverConfig.TrafficLimitStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serverMaps = append(serverMaps, maps.Map{
|
||||
"id": server.Id,
|
||||
"name": server.Name,
|
||||
"cname": cname,
|
||||
"tcpPorts": tcpPorts,
|
||||
"tlsPorts": tlsPorts,
|
||||
"udpPorts": udpPorts,
|
||||
"isOn": server.IsOn,
|
||||
"userPlan": userPlanMap,
|
||||
"trafficLimitStatus": trafficLimitStatus,
|
||||
})
|
||||
}
|
||||
this.Data["servers"] = serverMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
21
EdgeUser/internal/web/actions/default/lb/init.go
Normal file
21
EdgeUser/internal/web/actions/default/lb/init.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package lb
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth("")).
|
||||
Data("teaMenu", "lb").
|
||||
Prefix("/lb").
|
||||
GetPost("", new(IndexAction)).
|
||||
GetPost("/create", new(CreateAction)).
|
||||
Post("/updateOn", new(UpdateOnAction)).
|
||||
Post("/delete", new(DeleteAction)).
|
||||
Get("/server", new(ServerAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
21
EdgeUser/internal/web/actions/default/lb/server.go
Normal file
21
EdgeUser/internal/web/actions/default/lb/server.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package lb
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/utils/numberutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
type ServerAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *ServerAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *ServerAction) RunGet(params struct {
|
||||
ServerId int64
|
||||
}) {
|
||||
// TODO 先跳转到设置页面,将来实现日志查看、统计看板等
|
||||
this.RedirectURL("/lb/server/settings/basic?serverId=" + numberutils.FormatInt64(params.ServerId))
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "setting", "index")
|
||||
this.SecondMenu("basic")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
ServerId int64
|
||||
}) {
|
||||
resp, err := this.RPC().ServerRPC().FindEnabledUserServerBasic(this.UserContext(), &pb.FindEnabledUserServerBasicRequest{ServerId: params.ServerId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
server := resp.Server
|
||||
if server == nil {
|
||||
this.NotFound("server", params.ServerId)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["name"] = server.Name
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunPost(params struct {
|
||||
ServerId int64
|
||||
Name string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入服务名称")
|
||||
|
||||
_, err := this.RPC().ServerRPC().UpdateEnabledUserServerBasic(this.UserContext(), &pb.UpdateEnabledUserServerBasicRequest{
|
||||
ServerId: params.ServerId,
|
||||
Name: params.Name,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/lb/serverutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth("")).
|
||||
Helper(serverutils.NewServerHelper()).
|
||||
Prefix("/lb/server/settings/basic").
|
||||
GetPost("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "setting", "index")
|
||||
this.SecondMenu("dns")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
ServerId int64
|
||||
}) {
|
||||
dnsInfoResp, err := this.RPC().ServerRPC().FindEnabledServerDNS(this.UserContext(), &pb.FindEnabledServerDNSRequest{ServerId: params.ServerId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["dnsName"] = dnsInfoResp.DnsName
|
||||
if dnsInfoResp.Domain != nil {
|
||||
this.Data["dnsDomain"] = dnsInfoResp.Domain.Name
|
||||
} else {
|
||||
this.Data["dnsDomain"] = ""
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/actions/default/lb/serverutils"
|
||||
"github.com/TeaOSLab/EdgeUser/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth("")).
|
||||
Helper(serverutils.NewServerHelper()).
|
||||
Prefix("/lb/server/settings/dns").
|
||||
GetPost("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user