Initial commit (code only without large binaries)
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package actionutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type ActionInterface interface {
|
||||
RPC() *rpc.RPCClient
|
||||
|
||||
AdminContext() context.Context
|
||||
|
||||
ViewData() maps.Map
|
||||
}
|
||||
22
EdgeAdmin/internal/web/actions/actionutils/csrf.go
Normal file
22
EdgeAdmin/internal/web/actions/actionutils/csrf.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package actionutils
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/csrf"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type CSRF struct {
|
||||
}
|
||||
|
||||
func (this *CSRF) BeforeAction(actionPtr actions.ActionWrapper, paramName string) (goNext bool) {
|
||||
action := actionPtr.Object()
|
||||
token := action.ParamString("csrfToken")
|
||||
if !csrf.Validate(token) {
|
||||
action.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||
action.WriteString("表单已失效,请刷新页面后重试(001)")
|
||||
return
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
44
EdgeAdmin/internal/web/actions/actionutils/menu.go
Normal file
44
EdgeAdmin/internal/web/actions/actionutils/menu.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package actionutils
|
||||
|
||||
// Menu 子菜单定义
|
||||
type Menu struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Items []*MenuItem `json:"items"`
|
||||
IsActive bool `json:"isActive"`
|
||||
AlwaysActive bool `json:"alwaysActive"`
|
||||
Index int `json:"index"`
|
||||
CountNormalItems int `json:"countNormalItems"`
|
||||
}
|
||||
|
||||
// NewMenu 获取新对象
|
||||
func NewMenu() *Menu {
|
||||
return &Menu{
|
||||
Items: []*MenuItem{},
|
||||
}
|
||||
}
|
||||
|
||||
// Add 添加菜单项
|
||||
func (this *Menu) Add(name string, subName string, url string, isActive bool) *MenuItem {
|
||||
item := &MenuItem{
|
||||
Name: name,
|
||||
SubName: subName,
|
||||
URL: url,
|
||||
IsActive: isActive,
|
||||
}
|
||||
this.CountNormalItems++
|
||||
this.Items = append(this.Items, item)
|
||||
|
||||
if isActive {
|
||||
this.IsActive = true
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
// AddSpecial 添加特殊菜单项,不计数
|
||||
func (this *Menu) AddSpecial(name string, subName string, url string, isActive bool) *MenuItem {
|
||||
item := this.Add(name, subName, url, isActive)
|
||||
this.CountNormalItems--
|
||||
return item
|
||||
}
|
||||
48
EdgeAdmin/internal/web/actions/actionutils/menu_group.go
Normal file
48
EdgeAdmin/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"
|
||||
)
|
||||
|
||||
// MenuGroup 菜单分组
|
||||
type MenuGroup struct {
|
||||
Menus []*Menu `json:"menus"`
|
||||
AlwaysMenu *Menu `json:"alwaysMenu"`
|
||||
}
|
||||
|
||||
// NewMenuGroup 获取新菜单分组对象
|
||||
func NewMenuGroup() *MenuGroup {
|
||||
return &MenuGroup{
|
||||
Menus: []*Menu{},
|
||||
}
|
||||
}
|
||||
|
||||
// FindMenu 查找菜单,如果找不到则自动创建
|
||||
func (this *MenuGroup) FindMenu(menuId string, menuName string) *Menu {
|
||||
for _, m := range this.Menus {
|
||||
if m.Id == menuId {
|
||||
return m
|
||||
}
|
||||
}
|
||||
menu := NewMenu()
|
||||
menu.Id = menuId
|
||||
menu.Name = menuName
|
||||
menu.Items = []*MenuItem{}
|
||||
this.Menus = append(this.Menus, menu)
|
||||
return menu
|
||||
}
|
||||
|
||||
// Sort 排序
|
||||
func (this *MenuGroup) Sort() {
|
||||
lists.Sort(this.Menus, func(i int, j int) bool {
|
||||
menu1 := this.Menus[i]
|
||||
menu2 := this.Menus[j]
|
||||
return menu1.Index < menu2.Index
|
||||
})
|
||||
}
|
||||
|
||||
// SetSubMenu 设置子菜单
|
||||
func SetSubMenu(action actions.ActionWrapper, menu *MenuGroup) {
|
||||
action.Object().Data["teaSubMenus"] = menu
|
||||
}
|
||||
14
EdgeAdmin/internal/web/actions/actionutils/menu_item.go
Normal file
14
EdgeAdmin/internal/web/actions/actionutils/menu_item.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package actionutils
|
||||
|
||||
// MenuItem 菜单项
|
||||
type MenuItem struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
SubName string `json:"subName"` // 副标题
|
||||
SupName string `json:"supName"` // 头标
|
||||
URL string `json:"url"`
|
||||
IsActive bool `json:"isActive"`
|
||||
Icon string `json:"icon"`
|
||||
IsSortable bool `json:"isSortable"`
|
||||
SubColor string `json:"subColor"`
|
||||
}
|
||||
161
EdgeAdmin/internal/web/actions/actionutils/page.go
Normal file
161
EdgeAdmin/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="padding-top:0;padding-bottom:0;margin-left:1em;color:#666" onchange="ChangePageSize(this.value)">
|
||||
<option value="10">[每页]</option>`+this.renderSizeOption(10)+
|
||||
this.renderSizeOption(20)+
|
||||
this.renderSizeOption(30)+
|
||||
this.renderSizeOption(40)+
|
||||
this.renderSizeOption(50)+
|
||||
this.renderSizeOption(60)+
|
||||
this.renderSizeOption(70)+
|
||||
this.renderSizeOption(80)+
|
||||
this.renderSizeOption(90)+
|
||||
this.renderSizeOption(100)+`
|
||||
</select>`)
|
||||
|
||||
return `<div class="page">` + strings.Join(result, "") + `</div>`
|
||||
}
|
||||
|
||||
// IsLastPage 判断是否为最后一页
|
||||
func (this *Page) IsLastPage() bool {
|
||||
return this.Current == this.Max
|
||||
}
|
||||
|
||||
func (this *Page) composeURL(page int64) string {
|
||||
this.Query["page"] = []string{fmt.Sprintf("%d", page)}
|
||||
return this.Path + "?" + this.Query.Encode()
|
||||
}
|
||||
|
||||
func (this *Page) min(i, j int64) int64 {
|
||||
if i < j {
|
||||
return i
|
||||
}
|
||||
return j
|
||||
}
|
||||
|
||||
func (this *Page) max(i, j int64) int64 {
|
||||
if i < j {
|
||||
return j
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (this *Page) renderSizeOption(size int64) string {
|
||||
o := `<option value="` + fmt.Sprintf("%d", size) + `"`
|
||||
if size == this.Size {
|
||||
o += ` selected="selected"`
|
||||
}
|
||||
o += `>` + fmt.Sprintf("%d", size) + `条</option>`
|
||||
return o
|
||||
}
|
||||
40
EdgeAdmin/internal/web/actions/actionutils/page_test.go
Normal file
40
EdgeAdmin/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)
|
||||
}
|
||||
182
EdgeAdmin/internal/web/actions/actionutils/parent_action.go
Normal file
182
EdgeAdmin/internal/web/actions/actionutils/parent_action.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package actionutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index/loginutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ParentAction struct {
|
||||
actions.ActionObject
|
||||
|
||||
rpcClient *rpc.RPCClient
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// Parent 可以调用自身的一个简便方法
|
||||
func (this *ParentAction) Parent() *ParentAction {
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *ParentAction) ErrorPage(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 日志
|
||||
this.CreateLog(oplogs.LevelError, codes.AdminCommon_LogSystemError, err.Error())
|
||||
|
||||
if this.Request.Method == http.MethodGet {
|
||||
FailPage(this, err)
|
||||
} else {
|
||||
Fail(this, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ParentAction) ErrorText(err string) {
|
||||
this.ErrorPage(errors.New(err))
|
||||
}
|
||||
|
||||
func (this *ParentAction) NotFound(name string, itemId int64) {
|
||||
if itemId > 0 {
|
||||
this.ErrorPage(errors.New(name + " id: '" + strconv.FormatInt(itemId, 10) + "' is not found"))
|
||||
} else {
|
||||
this.ErrorPage(errors.New(name + " is not found"))
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ParentAction) NewPage(total int64, size ...int64) *Page {
|
||||
if len(size) > 0 {
|
||||
return NewActionPage(this, total, size[0])
|
||||
}
|
||||
|
||||
var pageSize int64 = 10
|
||||
adminConfig, err := configloaders.LoadAdminUIConfig()
|
||||
if err == nil && adminConfig.DefaultPageSize > 0 {
|
||||
pageSize = int64(adminConfig.DefaultPageSize)
|
||||
}
|
||||
|
||||
return NewActionPage(this, total, pageSize)
|
||||
}
|
||||
|
||||
func (this *ParentAction) Nav(mainMenu string, tab string, firstMenu string) {
|
||||
this.Data["mainMenu"] = mainMenu
|
||||
this.Data["mainTab"] = tab
|
||||
this.Data["firstMenuItem"] = firstMenu
|
||||
}
|
||||
|
||||
func (this *ParentAction) FirstMenu(menuItem string) {
|
||||
this.Data["firstMenuItem"] = menuItem
|
||||
}
|
||||
|
||||
func (this *ParentAction) SecondMenu(menuItem string) {
|
||||
this.Data["secondMenuItem"] = menuItem
|
||||
}
|
||||
|
||||
func (this *ParentAction) TinyMenu(menuItem string) {
|
||||
this.Data["tinyMenuItem"] = menuItem
|
||||
}
|
||||
|
||||
func (this *ParentAction) AdminId() int64 {
|
||||
return this.Context.GetInt64(teaconst.SessionAdminId)
|
||||
}
|
||||
|
||||
func (this *ParentAction) CreateLog(level string, messageCode langs.MessageCode, args ...any) {
|
||||
var description = messageCode.For(this.LangCode())
|
||||
var desc = fmt.Sprintf(description, args...)
|
||||
if level == oplogs.LevelInfo {
|
||||
if this.Code != 200 {
|
||||
level = oplogs.LevelWarn
|
||||
if len(this.Message) > 0 {
|
||||
desc += " 失败:" + this.Message
|
||||
}
|
||||
}
|
||||
}
|
||||
err := dao.SharedLogDAO.CreateAdminLog(this.AdminContext(), level, this.Request.URL.Path, desc, loginutils.RemoteIP(&this.ActionObject), messageCode, args)
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ParentAction) CreateLogInfo(messageCode langs.MessageCode, args ...any) {
|
||||
this.CreateLog(oplogs.LevelInfo, messageCode, args...)
|
||||
}
|
||||
|
||||
// RPC 获取RPC
|
||||
func (this *ParentAction) RPC() *rpc.RPCClient {
|
||||
if this.rpcClient != nil {
|
||||
return this.rpcClient
|
||||
}
|
||||
|
||||
// 所有集群
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
logs.Fatal(err)
|
||||
return nil
|
||||
}
|
||||
this.rpcClient = rpcClient
|
||||
|
||||
return rpcClient
|
||||
}
|
||||
|
||||
// AdminContext 获取Context
|
||||
// 每个请求的context都必须是一个新的实例
|
||||
func (this *ParentAction) AdminContext() context.Context {
|
||||
if this.rpcClient == nil {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
logs.Fatal(err)
|
||||
return nil
|
||||
}
|
||||
this.rpcClient = rpcClient
|
||||
}
|
||||
this.ctx = this.rpcClient.Context(this.AdminId())
|
||||
return this.ctx
|
||||
}
|
||||
|
||||
// ViewData 视图里可以使用的数据
|
||||
func (this *ParentAction) ViewData() maps.Map {
|
||||
return this.Data
|
||||
}
|
||||
|
||||
func (this *ParentAction) LangCode() string {
|
||||
return configloaders.FindAdminLangForAction(this)
|
||||
}
|
||||
|
||||
func (this *ParentAction) Lang(messageCode langs.MessageCode, args ...any) string {
|
||||
return langs.Message(this.LangCode(), messageCode, args...)
|
||||
}
|
||||
|
||||
func (this *ParentAction) FailLang(messageCode langs.MessageCode, args ...any) {
|
||||
this.Fail(langs.Message(this.LangCode(), messageCode, args...))
|
||||
}
|
||||
|
||||
func (this *ParentAction) FailFieldLang(field string, messageCode langs.MessageCode, args ...any) {
|
||||
this.FailField(field, langs.Message(this.LangCode(), messageCode, args...))
|
||||
}
|
||||
|
||||
func (this *ParentAction) FilterHTTPFamily() bool {
|
||||
if this.Data.GetString("serverFamily") == "http" {
|
||||
return false
|
||||
}
|
||||
|
||||
this.ResponseWriter.WriteHeader(http.StatusNotFound)
|
||||
_, _ = this.ResponseWriter.Write([]byte("page not found"))
|
||||
|
||||
return true
|
||||
}
|
||||
54
EdgeAdmin/internal/web/actions/actionutils/tabbar.go
Normal file
54
EdgeAdmin/internal/web/actions/actionutils/tabbar.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package actionutils
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type TabItem struct {
|
||||
Name string `json:"name"`
|
||||
SubName string `json:"subName"`
|
||||
URL string `json:"url"`
|
||||
Icon string `json:"icon"`
|
||||
IsActive bool `json:"isActive"`
|
||||
IsRight bool `json:"isRight"`
|
||||
IsTitle bool `json:"isTitle"`
|
||||
IsDisabled bool `json:"isDisabled"`
|
||||
}
|
||||
|
||||
// Tabbar Tabbar定义
|
||||
type Tabbar struct {
|
||||
items []*TabItem
|
||||
}
|
||||
|
||||
// NewTabbar 获取新对象
|
||||
func NewTabbar() *Tabbar {
|
||||
return &Tabbar{
|
||||
items: []*TabItem{},
|
||||
}
|
||||
}
|
||||
|
||||
// Add 添加菜单项
|
||||
func (this *Tabbar) Add(name string, subName string, url string, icon string, active bool) *TabItem {
|
||||
var m = &TabItem{
|
||||
Name: name,
|
||||
SubName: subName,
|
||||
URL: url,
|
||||
Icon: icon,
|
||||
IsActive: active,
|
||||
IsRight: false,
|
||||
IsTitle: false,
|
||||
IsDisabled: false,
|
||||
}
|
||||
this.items = append(this.items, m)
|
||||
return m
|
||||
}
|
||||
|
||||
// Items 取得所有的Items
|
||||
func (this *Tabbar) Items() []*TabItem {
|
||||
return this.items
|
||||
}
|
||||
|
||||
// SetTabbar 设置子菜单
|
||||
func SetTabbar(action actions.ActionWrapper, tabbar *Tabbar) {
|
||||
action.Object().Data["teaTabbar"] = tabbar.Items()
|
||||
}
|
||||
208
EdgeAdmin/internal/web/actions/actionutils/utils.go
Normal file
208
EdgeAdmin/internal/web/actions/actionutils/utils.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package actionutils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
rpcerrors "github.com/TeaOSLab/EdgeCommon/pkg/rpc/errors"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Fail 提示服务器错误信息
|
||||
func Fail(actionPtr actions.ActionWrapper, err error) {
|
||||
if err == nil {
|
||||
err = errors.New("unknown error")
|
||||
}
|
||||
|
||||
var langCode = configloaders.FindAdminLangForAction(actionPtr)
|
||||
var serverErrString = codes.AdminCommon_ServerError.For(langCode)
|
||||
|
||||
logs.Println("[" + reflect.TypeOf(actionPtr).String() + "]" + findStack(err.Error()))
|
||||
|
||||
_, _, isLocalAPI, issuesHTML := parseAPIErr(actionPtr, err)
|
||||
if isLocalAPI && len(issuesHTML) > 0 {
|
||||
actionPtr.Object().Fail(serverErrString + "(" + err.Error() + ";最近一次错误提示:" + issuesHTML + ")")
|
||||
} else {
|
||||
actionPtr.Object().Fail(serverErrString + "(" + err.Error() + ")")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// FailPage 提示页面错误信息
|
||||
func FailPage(actionPtr actions.ActionWrapper, err error) {
|
||||
if err == nil {
|
||||
err = errors.New("unknown error")
|
||||
}
|
||||
|
||||
var langCode = configloaders.FindAdminLangForAction(actionPtr)
|
||||
var serverErrString = codes.AdminCommon_ServerError.For(langCode)
|
||||
|
||||
logs.Println("[" + reflect.TypeOf(actionPtr).String() + "]" + findStack(err.Error()))
|
||||
|
||||
actionPtr.Object().ResponseWriter.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
if len(actionPtr.Object().Request.Header.Get("X-Requested-With")) > 0 {
|
||||
actionPtr.Object().WriteString(serverErrString)
|
||||
} else {
|
||||
apiNodeIsStarting, apiNodeProgress, _, issuesHTML := parseAPIErr(actionPtr, err)
|
||||
var html = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>正在处理...</title>
|
||||
<meta charset="UTF-8"/>
|
||||
<style type="text/css">
|
||||
hr { border-top: 1px #ccc solid; }
|
||||
.red { color: red; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background: #eee; border: 1px #ccc solid; padding: 10px; font-size: 12px; line-height: 1.8">
|
||||
`
|
||||
if apiNodeIsStarting { // API节点正在启动
|
||||
html += "<div class=\"red\">API节点正在启动,请耐心等待完成"
|
||||
|
||||
if len(apiNodeProgress) > 0 {
|
||||
html += ":" + apiNodeProgress + "(刷新当前页面查看最新状态)"
|
||||
}
|
||||
|
||||
html += "</div>"
|
||||
} else {
|
||||
html += serverErrString + `
|
||||
<div>可以通过查看 <strong><em>$安装目录/logs/run.log</em></strong> 日志文件查看具体的错误提示。</div>
|
||||
<hr/>
|
||||
<div class="red">Error: ` + err.Error() + `</div>`
|
||||
|
||||
if len(issuesHTML) > 0 {
|
||||
html += ` <hr/>
|
||||
<div class="red">` + issuesHTML + `</div>`
|
||||
}
|
||||
}
|
||||
|
||||
actionPtr.Object().WriteString(html + `
|
||||
</div>
|
||||
</body>
|
||||
</html>`)
|
||||
}
|
||||
}
|
||||
|
||||
// MatchPath 判断动作的文件路径是否相当
|
||||
func MatchPath(action *actions.ActionObject, path string) bool {
|
||||
return action.Request.URL.Path == path
|
||||
}
|
||||
|
||||
// FindParentAction 查找父级Action
|
||||
func FindParentAction(actionPtr actions.ActionWrapper) *ParentAction {
|
||||
action, ok := actionPtr.(interface {
|
||||
Parent() *ParentAction
|
||||
})
|
||||
if ok {
|
||||
return action.Parent()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findStack(err string) string {
|
||||
_, currentFilename, _, currentOk := runtime.Caller(1)
|
||||
if currentOk {
|
||||
for i := 1; i < 32; i++ {
|
||||
_, filename, lineNo, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
if filename == currentFilename || filepath.Base(filename) == "parent_action.go" {
|
||||
continue
|
||||
}
|
||||
|
||||
goPath := os.Getenv("GOPATH")
|
||||
if len(goPath) > 0 {
|
||||
absGoPath, err := filepath.Abs(goPath)
|
||||
if err == nil {
|
||||
filename = strings.TrimPrefix(filename, absGoPath)[1:]
|
||||
}
|
||||
} else if strings.Contains(filename, "src") {
|
||||
filename = filename[strings.Index(filename, "src"):]
|
||||
}
|
||||
|
||||
err += "\n\t\t" + filename + ":" + fmt.Sprintf("%d", lineNo)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// 分析API节点的错误信息
|
||||
func parseAPIErr(action actions.ActionWrapper, err error) (apiNodeIsStarting bool, apiNodeProgress string, isLocalAPI bool, issuesHTML string) {
|
||||
// 当前API终端地址
|
||||
var apiEndpoints = []string{}
|
||||
apiConfig, apiConfigErr := configs.LoadAPIConfig()
|
||||
if apiConfigErr == nil && apiConfig != nil {
|
||||
apiEndpoints = append(apiEndpoints, apiConfig.RPCEndpoints...)
|
||||
}
|
||||
|
||||
var isRPCConnError bool
|
||||
_, isRPCConnError = rpcerrors.HumanError(err, apiEndpoints, Tea.ConfigFile(configs.ConfigFileName))
|
||||
if isRPCConnError {
|
||||
// API节点是否正在启动
|
||||
var sock = gosock.NewTmpSock("edge-api")
|
||||
reply, err := sock.SendTimeout(&gosock.Command{
|
||||
Code: "starting",
|
||||
Params: nil,
|
||||
}, 1*time.Second)
|
||||
if err == nil && reply != nil {
|
||||
var params = maps.NewMap(reply.Params)
|
||||
if params.GetBool("isStarting") {
|
||||
apiNodeIsStarting = true
|
||||
|
||||
var progressMap = params.GetMap("progress")
|
||||
apiNodeProgress = progressMap.GetString("description")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 本地的一些错误提示
|
||||
if isRPCConnError {
|
||||
host, _, hostErr := net.SplitHostPort(action.Object().Request.Host)
|
||||
if hostErr == nil {
|
||||
for _, endpoint := range apiEndpoints {
|
||||
if strings.HasPrefix(endpoint, "http://"+host) || strings.HasPrefix(endpoint, "https://"+host) || strings.HasPrefix(endpoint, host) {
|
||||
isLocalAPI = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isLocalAPI {
|
||||
// 读取本地API节点的issues
|
||||
issuesData, issuesErr := os.ReadFile(Tea.Root + "/edge-api/logs/issues.log")
|
||||
if issuesErr == nil {
|
||||
var issueMaps = []maps.Map{}
|
||||
issuesErr = json.Unmarshal(issuesData, &issueMaps)
|
||||
if issuesErr == nil && len(issueMaps) > 0 {
|
||||
var issueMap = issueMaps[0]
|
||||
issuesHTML = "本地API节点启动错误:" + issueMap.GetString("message") + ",处理建议:" + issueMap.GetString("suggestion")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user