Initial commit (code only without large binaries)
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type DeleteAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *DeleteAction) RunPost(params struct {
|
||||
NodeId int64
|
||||
}) {
|
||||
// 创建日志
|
||||
defer this.CreateLogInfo(codes.APINode_LogDeleteAPINode, params.NodeId)
|
||||
|
||||
// 检查是否是唯一的节点
|
||||
nodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{ApiNodeId: params.NodeId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var apiNode = nodeResp.ApiNode
|
||||
if apiNode == nil {
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
if apiNode.IsOn {
|
||||
countResp, err := this.RPC().APINodeRPC().CountAllEnabledAndOnAPINodes(this.AdminContext(), &pb.CountAllEnabledAndOnAPINodesRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if countResp.Count == 1 {
|
||||
this.Fail("无法删除此节点:必须保留至少一个可用的API节点")
|
||||
}
|
||||
}
|
||||
|
||||
_, err = this.RPC().APINodeRPC().DeleteAPINode(this.AdminContext(), &pb.DeleteAPINodeRequest{ApiNodeId: params.NodeId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type Helper struct {
|
||||
}
|
||||
|
||||
func NewHelper() *Helper {
|
||||
return &Helper{}
|
||||
}
|
||||
|
||||
func (this *Helper) BeforeAction(action *actions.ActionObject) {
|
||||
}
|
||||
130
EdgeAdmin/internal/web/actions/default/settings/api/index.go
Normal file
130
EdgeAdmin/internal/web/actions/default/settings/api/index.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/apinodeutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "node", "index")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
countResp, err := this.RPC().APINodeRPC().CountAllEnabledAPINodes(this.AdminContext(), &pb.CountAllEnabledAPINodesRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var count = countResp.Count
|
||||
var page = this.NewPage(count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
var nodeMaps = []maps.Map{}
|
||||
if count > 0 {
|
||||
nodesResp, err := this.RPC().APINodeRPC().ListEnabledAPINodes(this.AdminContext(), &pb.ListEnabledAPINodesRequest{
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, node := range nodesResp.ApiNodes {
|
||||
// 状态
|
||||
var status = &nodeconfigs.NodeStatus{}
|
||||
if len(node.StatusJSON) > 0 {
|
||||
err = json.Unmarshal(node.StatusJSON, &status)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
continue
|
||||
}
|
||||
status.IsActive = status.IsActive && time.Now().Unix()-status.UpdatedAt <= 60 // N秒之内认为活跃
|
||||
}
|
||||
|
||||
// Rest地址
|
||||
var restAccessAddrs = []string{}
|
||||
if node.RestIsOn {
|
||||
if len(node.RestHTTPJSON) > 0 {
|
||||
httpConfig := &serverconfigs.HTTPProtocolConfig{}
|
||||
err = json.Unmarshal(node.RestHTTPJSON, httpConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
_ = httpConfig.Init()
|
||||
if httpConfig.IsOn && len(httpConfig.Listen) > 0 {
|
||||
for _, listen := range httpConfig.Listen {
|
||||
restAccessAddrs = append(restAccessAddrs, listen.FullAddresses()...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(node.RestHTTPSJSON) > 0 {
|
||||
httpsConfig := &serverconfigs.HTTPSProtocolConfig{}
|
||||
err = json.Unmarshal(node.RestHTTPSJSON, httpsConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
_ = httpsConfig.Init(context.TODO())
|
||||
if httpsConfig.IsOn && len(httpsConfig.Listen) > 0 {
|
||||
restAccessAddrs = append(restAccessAddrs, httpsConfig.FullAddresses()...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var shouldUpgrade = status.IsActive && len(status.BuildVersion) > 0 && stringutil.VersionCompare(teaconst.APINodeVersion, status.BuildVersion) > 0
|
||||
canUpgrade, _ := apinodeutils.CanUpgrade(status.BuildVersion, status.OS, status.Arch)
|
||||
|
||||
nodeMaps = append(nodeMaps, maps.Map{
|
||||
"id": node.Id,
|
||||
"isOn": node.IsOn,
|
||||
"name": node.Name,
|
||||
"accessAddrs": node.AccessAddrs,
|
||||
"restAccessAddrs": restAccessAddrs,
|
||||
"isPrimary": node.IsPrimary,
|
||||
"status": maps.Map{
|
||||
"isActive": status.IsActive,
|
||||
"updatedAt": status.UpdatedAt,
|
||||
"hostname": status.Hostname,
|
||||
"cpuUsage": status.CPUUsage,
|
||||
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
|
||||
"memUsage": status.MemoryUsage,
|
||||
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
|
||||
"buildVersion": status.BuildVersion,
|
||||
"latestVersion": teaconst.APINodeVersion,
|
||||
"shouldUpgrade": shouldUpgrade,
|
||||
"canUpgrade": shouldUpgrade && canUpgrade,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
this.Data["nodes"] = nodeMaps
|
||||
|
||||
// 检查是否有调试数据
|
||||
countMethodStatsResp, err := this.RPC().APIMethodStatRPC().CountAPIMethodStatsWithDay(this.AdminContext(), &pb.CountAPIMethodStatsWithDayRequest{Day: timeutil.Format("Ymd")})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["hasMethodStats"] = countMethodStatsResp.Count > 0
|
||||
|
||||
this.Show()
|
||||
}
|
||||
24
EdgeAdmin/internal/web/actions/default/settings/api/init.go
Normal file
24
EdgeAdmin/internal/web/actions/default/settings/api/init.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/api/node"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/settingutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)).
|
||||
Helper(NewHelper()).
|
||||
Helper(settingutils.NewAdvancedHelper("apiNodes")).
|
||||
Prefix("/settings/api").
|
||||
Get("", new(IndexAction)).
|
||||
Get("/methodStats", new(MethodStatsAction)).
|
||||
GetPost("/node/createPopup", new(node.CreatePopupAction)).
|
||||
Post("/delete", new(DeleteAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type MethodStatsAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *MethodStatsAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *MethodStatsAction) RunGet(params struct {
|
||||
Order string
|
||||
Method string
|
||||
Tag string
|
||||
}) {
|
||||
this.Data["order"] = params.Order
|
||||
this.Data["method"] = params.Method
|
||||
this.Data["tag"] = params.Tag
|
||||
|
||||
statsResp, err := this.RPC().APIMethodStatRPC().FindAPIMethodStatsWithDay(this.AdminContext(), &pb.FindAPIMethodStatsWithDayRequest{Day: timeutil.Format("Ymd")})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var pbStats = statsResp.ApiMethodStats
|
||||
|
||||
switch params.Order {
|
||||
case "method":
|
||||
sort.Slice(pbStats, func(i, j int) bool {
|
||||
return pbStats[i].Method < pbStats[j].Method
|
||||
})
|
||||
case "costMs.desc":
|
||||
sort.Slice(pbStats, func(i, j int) bool {
|
||||
return pbStats[i].CostMs > pbStats[j].CostMs
|
||||
})
|
||||
case "peekMs.desc":
|
||||
sort.Slice(pbStats, func(i, j int) bool {
|
||||
return pbStats[i].PeekMs > pbStats[j].PeekMs
|
||||
})
|
||||
case "calls.desc":
|
||||
sort.Slice(pbStats, func(i, j int) bool {
|
||||
return pbStats[i].CountCalls > pbStats[j].CountCalls
|
||||
})
|
||||
}
|
||||
|
||||
var statMaps = []maps.Map{}
|
||||
for _, stat := range pbStats {
|
||||
if len(params.Method) > 0 && !strings.Contains(strings.ToLower(stat.Method), strings.ToLower(params.Method)) {
|
||||
continue
|
||||
}
|
||||
if len(params.Tag) > 0 && !strings.Contains(strings.ToLower(stat.Tag), strings.ToLower(params.Tag)) {
|
||||
continue
|
||||
}
|
||||
|
||||
statMaps = append(statMaps, maps.Map{
|
||||
"id": stat.Id,
|
||||
"method": stat.Method,
|
||||
"tag": stat.Tag,
|
||||
"costMs": stat.CostMs,
|
||||
"peekMs": stat.PeekMs,
|
||||
"countCalls": stat.CountCalls,
|
||||
})
|
||||
}
|
||||
this.Data["stats"] = statMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CreateAddrPopupAction 添加地址
|
||||
type CreateAddrPopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *CreateAddrPopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *CreateAddrPopupAction) RunGet(params struct {
|
||||
}) {
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *CreateAddrPopupAction) RunPost(params struct {
|
||||
Protocol string
|
||||
Addr string
|
||||
Must *actions.Must
|
||||
}) {
|
||||
params.Must.
|
||||
Field("addr", params.Addr).
|
||||
Require("请输入访问地址")
|
||||
|
||||
// 兼容URL
|
||||
if regexp.MustCompile(`^(?i)(http|https)://`).MatchString(params.Addr) {
|
||||
u, err := url.Parse(params.Addr)
|
||||
if err != nil {
|
||||
this.FailField("addr", "错误的访问地址,不需要添加http://或https://")
|
||||
}
|
||||
params.Addr = u.Host
|
||||
}
|
||||
|
||||
// 自动添加端口
|
||||
if !strings.Contains(params.Addr, ":") {
|
||||
switch params.Protocol {
|
||||
case "http":
|
||||
params.Addr += ":80"
|
||||
case "https":
|
||||
params.Addr += ":443"
|
||||
}
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(params.Addr)
|
||||
if err != nil {
|
||||
this.FailField("addr", "错误的访问地址")
|
||||
}
|
||||
|
||||
var addrConfig = &serverconfigs.NetworkAddressConfig{
|
||||
Protocol: serverconfigs.Protocol(params.Protocol),
|
||||
Host: host,
|
||||
PortRange: port,
|
||||
}
|
||||
this.Data["addr"] = addrConfig
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"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/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
)
|
||||
|
||||
type CreatePopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) Init() {
|
||||
this.Nav("", "node", "create")
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunGet(params struct{}) {
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunPost(params struct {
|
||||
Name string
|
||||
Description string
|
||||
ListensJSON []byte
|
||||
CertIdsJSON []byte
|
||||
AccessAddrsJSON []byte
|
||||
|
||||
RestIsOn bool
|
||||
RestListensJSON []byte
|
||||
|
||||
IsOn bool
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入API节点名称")
|
||||
|
||||
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
|
||||
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
|
||||
|
||||
// 监听地址
|
||||
var listens = []*serverconfigs.NetworkAddressConfig{}
|
||||
err := json.Unmarshal(params.ListensJSON, &listens)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if len(listens) == 0 {
|
||||
this.Fail("请添加至少一个进程监听地址")
|
||||
}
|
||||
for _, addr := range listens {
|
||||
if addr.Protocol.IsHTTPFamily() {
|
||||
httpConfig.IsOn = true
|
||||
httpConfig.Listen = append(httpConfig.Listen, addr)
|
||||
} else if addr.Protocol.IsHTTPSFamily() {
|
||||
httpsConfig.IsOn = true
|
||||
httpsConfig.Listen = append(httpsConfig.Listen, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// Rest监听地址
|
||||
var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
|
||||
var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{}
|
||||
if params.RestIsOn {
|
||||
var restListens = []*serverconfigs.NetworkAddressConfig{}
|
||||
err = json.Unmarshal(params.RestListensJSON, &restListens)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if len(restListens) == 0 {
|
||||
this.Fail("请至少添加一个HTTP API监听端口")
|
||||
return
|
||||
}
|
||||
for _, addr := range restListens {
|
||||
if addr.Protocol.IsHTTPFamily() {
|
||||
restHTTPConfig.IsOn = true
|
||||
restHTTPConfig.Listen = append(restHTTPConfig.Listen, addr)
|
||||
} else if addr.Protocol.IsHTTPSFamily() {
|
||||
restHTTPSConfig.IsOn = true
|
||||
restHTTPSConfig.Listen = append(restHTTPSConfig.Listen, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// 是否有端口冲突
|
||||
var rpcAddresses = []string{}
|
||||
for _, listen := range listens {
|
||||
err := listen.Init()
|
||||
if err != nil {
|
||||
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
|
||||
return
|
||||
}
|
||||
rpcAddresses = append(rpcAddresses, listen.Addresses()...)
|
||||
}
|
||||
|
||||
for _, listen := range restListens {
|
||||
err := listen.Init()
|
||||
if err != nil {
|
||||
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
|
||||
return
|
||||
}
|
||||
for _, address := range listen.Addresses() {
|
||||
if lists.ContainsString(rpcAddresses, address) {
|
||||
this.Fail("HTTP API地址 '" + address + "' 和 GRPC地址冲突,请修改后提交")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 证书
|
||||
var certIds = []int64{}
|
||||
if len(params.CertIdsJSON) > 0 {
|
||||
err = json.Unmarshal(params.CertIdsJSON, &certIds)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if ((httpsConfig.IsOn && len(httpsConfig.Listen) > 0) || (restHTTPSConfig.IsOn && len(httpsConfig.Listen) > 0)) && len(certIds) == 0 {
|
||||
this.Fail("请添加至少一个证书")
|
||||
}
|
||||
|
||||
var 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
|
||||
}
|
||||
|
||||
// 创建策略
|
||||
if len(certIds) > 0 {
|
||||
sslPolicyCreateResp, err := this.RPC().SSLPolicyRPC().CreateSSLPolicy(this.AdminContext(), &pb.CreateSSLPolicyRequest{
|
||||
SslCertsJSON: certRefsJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
sslPolicyId := sslPolicyCreateResp.SslPolicyId
|
||||
httpsConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
|
||||
IsOn: true,
|
||||
SSLPolicyId: sslPolicyId,
|
||||
}
|
||||
restHTTPSConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
|
||||
IsOn: true,
|
||||
SSLPolicyId: sslPolicyId,
|
||||
}
|
||||
}
|
||||
|
||||
// 访问地址
|
||||
var accessAddrs = []*serverconfigs.NetworkAddressConfig{}
|
||||
err = json.Unmarshal(params.AccessAddrsJSON, &accessAddrs)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if len(accessAddrs) == 0 {
|
||||
this.Fail("请添加至少一个外部访问地址")
|
||||
}
|
||||
|
||||
httpJSON, err := json.Marshal(httpConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
httpsJSON, err := json.Marshal(httpsConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
restHTTPJSON, err := json.Marshal(restHTTPConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
restHTTPSJSON, err := json.Marshal(restHTTPSConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
createResp, err := this.RPC().APINodeRPC().CreateAPINode(this.AdminContext(), &pb.CreateAPINodeRequest{
|
||||
Name: params.Name,
|
||||
Description: params.Description,
|
||||
HttpJSON: httpJSON,
|
||||
HttpsJSON: httpsJSON,
|
||||
RestIsOn: params.RestIsOn,
|
||||
RestHTTPJSON: restHTTPJSON,
|
||||
RestHTTPSJSON: restHTTPSJSON,
|
||||
AccessAddrsJSON: params.AccessAddrsJSON,
|
||||
IsOn: params.IsOn,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建日志
|
||||
defer this.CreateLogInfo(codes.APINode_LogCreateAPINode, createResp.ApiNodeId)
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Helper struct {
|
||||
}
|
||||
|
||||
func NewHelper() *Helper {
|
||||
return &Helper{}
|
||||
}
|
||||
|
||||
func (this *Helper) BeforeAction(action *actions.ActionObject) (goNext bool) {
|
||||
if action.Request.Method != http.MethodGet {
|
||||
return true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "index")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
NodeId int64
|
||||
}) {
|
||||
nodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{ApiNodeId: params.NodeId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var node = nodeResp.ApiNode
|
||||
if node == nil {
|
||||
this.NotFound("apiNode", params.NodeId)
|
||||
return
|
||||
}
|
||||
|
||||
// 监听地址
|
||||
var hasHTTPS = false
|
||||
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
|
||||
if len(node.HttpJSON) > 0 {
|
||||
err = json.Unmarshal(node.HttpJSON, httpConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
|
||||
if len(node.HttpsJSON) > 0 {
|
||||
err = json.Unmarshal(node.HttpsJSON, httpsConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
hasHTTPS = len(httpsConfig.Listen) > 0
|
||||
}
|
||||
|
||||
// 监听地址
|
||||
var listens = []*serverconfigs.NetworkAddressConfig{}
|
||||
listens = append(listens, httpConfig.Listen...)
|
||||
listens = append(listens, httpsConfig.Listen...)
|
||||
|
||||
// 证书信息
|
||||
var certs = []*sslconfigs.SSLCertConfig{}
|
||||
if httpsConfig.SSLPolicyRef != nil && httpsConfig.SSLPolicyRef.SSLPolicyId > 0 {
|
||||
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{
|
||||
SslPolicyId: httpsConfig.SSLPolicyRef.SSLPolicyId,
|
||||
IgnoreData: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var sslPolicyConfigJSON = sslPolicyConfigResp.SslPolicyJSON
|
||||
if len(sslPolicyConfigJSON) > 0 {
|
||||
var sslPolicy = &sslconfigs.SSLPolicy{}
|
||||
err = json.Unmarshal(sslPolicyConfigJSON, sslPolicy)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
certs = sslPolicy.Certs
|
||||
}
|
||||
}
|
||||
|
||||
// 访问地址
|
||||
var accessAddrs = []*serverconfigs.NetworkAddressConfig{}
|
||||
if len(node.AccessAddrsJSON) > 0 {
|
||||
err = json.Unmarshal(node.AccessAddrsJSON, &accessAddrs)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Rest地址
|
||||
var restAccessAddrs = []*serverconfigs.NetworkAddressConfig{}
|
||||
if node.RestIsOn {
|
||||
if len(node.RestHTTPJSON) > 0 {
|
||||
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
|
||||
err = json.Unmarshal(node.RestHTTPJSON, httpConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if httpConfig.IsOn && len(httpConfig.Listen) > 0 {
|
||||
restAccessAddrs = append(restAccessAddrs, httpConfig.Listen...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(node.RestHTTPSJSON) > 0 {
|
||||
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
|
||||
err = json.Unmarshal(node.RestHTTPSJSON, httpsConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if httpsConfig.IsOn && len(httpsConfig.Listen) > 0 {
|
||||
restAccessAddrs = append(restAccessAddrs, httpsConfig.Listen...)
|
||||
}
|
||||
|
||||
if !hasHTTPS {
|
||||
hasHTTPS = len(httpsConfig.Listen) > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 状态
|
||||
var status = &nodeconfigs.NodeStatus{}
|
||||
var statusIsValid = false
|
||||
this.Data["newVersion"] = ""
|
||||
if len(node.StatusJSON) > 0 {
|
||||
err = json.Unmarshal(node.StatusJSON, &status)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
if status.UpdatedAt >= time.Now().Unix()-300 {
|
||||
statusIsValid = true
|
||||
|
||||
// 是否为新版本
|
||||
if stringutil.VersionCompare(status.BuildVersion, teaconst.APINodeVersion) < 0 {
|
||||
this.Data["newVersion"] = teaconst.APINodeVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["node"] = maps.Map{
|
||||
"id": node.Id,
|
||||
"name": node.Name,
|
||||
"description": node.Description,
|
||||
"isOn": node.IsOn,
|
||||
"listens": listens,
|
||||
"accessAddrs": accessAddrs,
|
||||
"restIsOn": node.RestIsOn,
|
||||
"restAccessAddrs": restAccessAddrs,
|
||||
"hasHTTPS": hasHTTPS,
|
||||
"certs": certs,
|
||||
"isPrimary": node.IsPrimary,
|
||||
"statusIsValid": statusIsValid,
|
||||
"status": maps.Map{
|
||||
"isActive": status.IsActive,
|
||||
"updatedAt": status.UpdatedAt,
|
||||
"hostname": status.Hostname,
|
||||
"cpuUsage": status.CPUUsage,
|
||||
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
|
||||
"memUsage": status.MemoryUsage,
|
||||
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
|
||||
"connectionCount": status.ConnectionCount,
|
||||
"buildVersion": status.BuildVersion,
|
||||
"cpuPhysicalCount": status.CPUPhysicalCount,
|
||||
"cpuLogicalCount": status.CPULogicalCount,
|
||||
"load1m": numberutils.FormatFloat2(status.Load1m),
|
||||
"load5m": numberutils.FormatFloat2(status.Load5m),
|
||||
"load15m": numberutils.FormatFloat2(status.Load15m),
|
||||
"cacheTotalDiskSize": numberutils.FormatBytes(status.CacheTotalDiskSize),
|
||||
"cacheTotalMemorySize": numberutils.FormatBytes(status.CacheTotalMemorySize),
|
||||
"exePath": status.ExePath,
|
||||
},
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/settingutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)).
|
||||
Helper(settingutils.NewAdvancedHelper("apiNodes")).
|
||||
Prefix("/settings/api/node").
|
||||
|
||||
// 这里不受Helper的约束
|
||||
GetPost("/createAddrPopup", new(CreateAddrPopupAction)).
|
||||
GetPost("/updateAddrPopup", new(UpdateAddrPopupAction)).
|
||||
|
||||
// 节点相关
|
||||
Helper(NewHelper()).
|
||||
Get("", new(IndexAction)).
|
||||
GetPost("/update", new(UpdateAction)).
|
||||
Get("/install", new(InstallAction)).
|
||||
Get("/logs", new(LogsAction)).
|
||||
GetPost("/upgradePopup", new(UpgradePopupAction)).
|
||||
Post("/upgradeCheck", new(UpgradeCheckAction)).
|
||||
|
||||
//
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type InstallAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *InstallAction) Init() {
|
||||
this.Nav("", "", "install")
|
||||
}
|
||||
|
||||
func (this *InstallAction) RunGet(params struct {
|
||||
NodeId int64
|
||||
}) {
|
||||
// API节点信息
|
||||
nodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{ApiNodeId: params.NodeId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var node = nodeResp.ApiNode
|
||||
if node == nil {
|
||||
this.NotFound("apiNode", params.NodeId)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["node"] = maps.Map{
|
||||
"id": node.Id,
|
||||
"name": node.Name,
|
||||
"uniqueId": node.UniqueId,
|
||||
"secret": node.Secret,
|
||||
}
|
||||
|
||||
// 数据库配置
|
||||
var dbConfigMap = maps.Map{
|
||||
"config": "",
|
||||
"error": "",
|
||||
"isNotFound": false,
|
||||
}
|
||||
var dbConfigFile = Tea.ConfigFile("api_db.yaml")
|
||||
data, err := os.ReadFile(dbConfigFile)
|
||||
dbConfigMap["config"] = string(data)
|
||||
var config = &configs.SimpleDBConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err == nil && len(config.Host) > 0 {
|
||||
var password = config.Password
|
||||
if len(password) > 0 {
|
||||
password = strings.Repeat("*", len(password))
|
||||
}
|
||||
config.Password = password
|
||||
tmpConf, _ := yaml.Marshal(config)
|
||||
|
||||
dbConfigMap["config"] = string(tmpConf)
|
||||
} else {
|
||||
var config = &dbs.Config{}
|
||||
err := yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
this.Data["error"] = "parse config file failed: api_db.yaml: " + err.Error()
|
||||
this.Show()
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
this.Data["error"] = "parse config file failed: api_db.yaml: " + err.Error()
|
||||
this.Show()
|
||||
return
|
||||
}
|
||||
|
||||
if config.DBs == nil {
|
||||
this.Data["error"] = "no database configured in config file: api_db.yaml"
|
||||
this.Show()
|
||||
return
|
||||
}
|
||||
|
||||
for key, db := range config.DBs {
|
||||
var dbConfig *dbs.DBConfig
|
||||
dbConfig = db
|
||||
|
||||
if dbConfig == nil {
|
||||
this.Data["error"] = "no database configured in config file: api_db.yaml"
|
||||
this.Show()
|
||||
return
|
||||
}
|
||||
var dsn = dbConfig.Dsn
|
||||
cfg, err := mysql.ParseDSN(dsn)
|
||||
if err != nil {
|
||||
this.Data["error"] = "parse dsn error: " + err.Error()
|
||||
this.Show()
|
||||
return
|
||||
}
|
||||
var password = cfg.Passwd
|
||||
if len(password) > 0 {
|
||||
password = strings.Repeat("*", len(password))
|
||||
}
|
||||
|
||||
var host = cfg.Addr
|
||||
var port = "3306"
|
||||
var index = strings.LastIndex(host, ":")
|
||||
if index > 0 {
|
||||
port = host[index+1:]
|
||||
host = host[:index]
|
||||
}
|
||||
dbConfig.Dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&timeout=30s", cfg.User, password, host, port, cfg.DBName)
|
||||
config.DBs[key] = dbConfig
|
||||
//fmt.Println(host, port, cfg.User, password, cfg.DBName)
|
||||
}
|
||||
tmpConf, _ := yaml.Marshal(config)
|
||||
dbConfigMap["config"] = string(tmpConf)
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
dbConfigMap["error"] = err.Error()
|
||||
dbConfigMap["isNotFound"] = os.IsNotExist(err)
|
||||
}
|
||||
this.Data["dbConfig"] = dbConfigMap
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
type LogsAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *LogsAction) Init() {
|
||||
this.Nav("", "node", "log")
|
||||
this.SecondMenu("nodes")
|
||||
}
|
||||
|
||||
func (this *LogsAction) RunGet(params struct {
|
||||
NodeId int64
|
||||
|
||||
DayFrom string
|
||||
DayTo string
|
||||
Keyword string
|
||||
Level string
|
||||
}) {
|
||||
this.Data["nodeId"] = params.NodeId
|
||||
this.Data["dayFrom"] = params.DayFrom
|
||||
this.Data["dayTo"] = params.DayTo
|
||||
this.Data["keyword"] = params.Keyword
|
||||
this.Data["level"] = params.Level
|
||||
|
||||
apiNodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{ApiNodeId: params.NodeId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
apiNode := apiNodeResp.ApiNode
|
||||
if apiNode == nil {
|
||||
this.NotFound("apiNode", params.NodeId)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["node"] = maps.Map{
|
||||
"id": apiNode.Id,
|
||||
"name": apiNode.Name,
|
||||
}
|
||||
|
||||
countResp, err := this.RPC().NodeLogRPC().CountNodeLogs(this.AdminContext(), &pb.CountNodeLogsRequest{
|
||||
Role: nodeconfigs.NodeRoleAPI,
|
||||
NodeId: params.NodeId,
|
||||
DayFrom: params.DayFrom,
|
||||
DayTo: params.DayTo,
|
||||
Keyword: params.Keyword,
|
||||
Level: params.Level,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var count = countResp.Count
|
||||
var page = this.NewPage(count, 20)
|
||||
|
||||
logsResp, err := this.RPC().NodeLogRPC().ListNodeLogs(this.AdminContext(), &pb.ListNodeLogsRequest{
|
||||
NodeId: params.NodeId,
|
||||
Role: nodeconfigs.NodeRoleAPI,
|
||||
DayFrom: params.DayFrom,
|
||||
DayTo: params.DayTo,
|
||||
Keyword: params.Keyword,
|
||||
Level: params.Level,
|
||||
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var logs = []maps.Map{}
|
||||
for _, log := range logsResp.NodeLogs {
|
||||
logs = append(logs, maps.Map{
|
||||
"tag": log.Tag,
|
||||
"description": log.Description,
|
||||
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", log.CreatedAt),
|
||||
"level": log.Level,
|
||||
"isToday": timeutil.FormatTime("Y-m-d", log.CreatedAt) == timeutil.Format("Y-m-d"),
|
||||
})
|
||||
}
|
||||
this.Data["logs"] = logs
|
||||
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"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/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type UpdateAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpdateAction) Init() {
|
||||
this.Nav("", "", "update")
|
||||
}
|
||||
|
||||
func (this *UpdateAction) RunGet(params struct {
|
||||
NodeId int64
|
||||
}) {
|
||||
nodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{
|
||||
ApiNodeId: params.NodeId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var node = nodeResp.ApiNode
|
||||
if node == nil {
|
||||
this.WriteString("要操作的节点不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
|
||||
if len(node.HttpJSON) > 0 {
|
||||
err = json.Unmarshal(node.HttpJSON, httpConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
|
||||
if len(node.HttpsJSON) > 0 {
|
||||
err = json.Unmarshal(node.HttpsJSON, httpsConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 监听地址
|
||||
var listens = []*serverconfigs.NetworkAddressConfig{}
|
||||
listens = append(listens, httpConfig.Listen...)
|
||||
listens = append(listens, httpsConfig.Listen...)
|
||||
|
||||
var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
|
||||
if len(node.RestHTTPJSON) > 0 {
|
||||
err = json.Unmarshal(node.RestHTTPJSON, restHTTPConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{}
|
||||
if len(node.RestHTTPSJSON) > 0 {
|
||||
err = json.Unmarshal(node.RestHTTPSJSON, restHTTPSConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 监听地址
|
||||
var restListens = []*serverconfigs.NetworkAddressConfig{}
|
||||
restListens = append(restListens, restHTTPConfig.Listen...)
|
||||
restListens = append(restListens, restHTTPSConfig.Listen...)
|
||||
|
||||
// 证书信息
|
||||
var certs = []*sslconfigs.SSLCertConfig{}
|
||||
var sslPolicyId = int64(0)
|
||||
if httpsConfig.SSLPolicyRef != nil && httpsConfig.SSLPolicyRef.SSLPolicyId > 0 {
|
||||
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{
|
||||
SslPolicyId: httpsConfig.SSLPolicyRef.SSLPolicyId,
|
||||
IgnoreData: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var sslPolicyConfigJSON = sslPolicyConfigResp.SslPolicyJSON
|
||||
if len(sslPolicyConfigJSON) > 0 {
|
||||
sslPolicyId = httpsConfig.SSLPolicyRef.SSLPolicyId
|
||||
|
||||
sslPolicy := &sslconfigs.SSLPolicy{}
|
||||
err = json.Unmarshal(sslPolicyConfigJSON, sslPolicy)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
certs = sslPolicy.Certs
|
||||
}
|
||||
}
|
||||
|
||||
var accessAddrs = []*serverconfigs.NetworkAddressConfig{}
|
||||
if len(node.AccessAddrsJSON) > 0 {
|
||||
err = json.Unmarshal(node.AccessAddrsJSON, &accessAddrs)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["node"] = maps.Map{
|
||||
"id": node.Id,
|
||||
"name": node.Name,
|
||||
"description": node.Description,
|
||||
"isOn": node.IsOn,
|
||||
"listens": listens,
|
||||
"restIsOn": node.RestIsOn,
|
||||
"restListens": restListens,
|
||||
"certs": certs,
|
||||
"sslPolicyId": sslPolicyId,
|
||||
"accessAddrs": accessAddrs,
|
||||
"isPrimary": node.IsPrimary,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
// RunPost 保存基础设置
|
||||
func (this *UpdateAction) RunPost(params struct {
|
||||
NodeId int64
|
||||
Name string
|
||||
SslPolicyId int64
|
||||
ListensJSON []byte
|
||||
RestIsOn bool
|
||||
RestListensJSON []byte
|
||||
CertIdsJSON []byte
|
||||
AccessAddrsJSON []byte
|
||||
Description string
|
||||
IsOn bool
|
||||
IsPrimary bool
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入API节点名称")
|
||||
|
||||
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
|
||||
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
|
||||
|
||||
// 监听地址
|
||||
var listens = []*serverconfigs.NetworkAddressConfig{}
|
||||
err := json.Unmarshal(params.ListensJSON, &listens)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if len(listens) == 0 {
|
||||
this.Fail("请添加至少一个进程监听地址")
|
||||
}
|
||||
for _, addr := range listens {
|
||||
if addr.Protocol.IsHTTPFamily() {
|
||||
httpConfig.IsOn = true
|
||||
httpConfig.Listen = append(httpConfig.Listen, addr)
|
||||
} else if addr.Protocol.IsHTTPSFamily() {
|
||||
httpsConfig.IsOn = true
|
||||
httpsConfig.Listen = append(httpsConfig.Listen, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// Rest监听地址
|
||||
var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
|
||||
var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{}
|
||||
if params.RestIsOn {
|
||||
var restListens = []*serverconfigs.NetworkAddressConfig{}
|
||||
err = json.Unmarshal(params.RestListensJSON, &restListens)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if len(restListens) == 0 {
|
||||
this.Fail("请至少添加一个HTTP API监听端口")
|
||||
return
|
||||
}
|
||||
for _, addr := range restListens {
|
||||
if addr.Protocol.IsHTTPFamily() {
|
||||
restHTTPConfig.IsOn = true
|
||||
restHTTPConfig.Listen = append(restHTTPConfig.Listen, addr)
|
||||
} else if addr.Protocol.IsHTTPSFamily() {
|
||||
restHTTPSConfig.IsOn = true
|
||||
restHTTPSConfig.Listen = append(restHTTPSConfig.Listen, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// 是否有端口冲突
|
||||
var rpcAddresses = []string{}
|
||||
for _, listen := range listens {
|
||||
err := listen.Init()
|
||||
if err != nil {
|
||||
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
|
||||
return
|
||||
}
|
||||
rpcAddresses = append(rpcAddresses, listen.Addresses()...)
|
||||
}
|
||||
|
||||
for _, listen := range restListens {
|
||||
err := listen.Init()
|
||||
if err != nil {
|
||||
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
|
||||
return
|
||||
}
|
||||
for _, address := range listen.Addresses() {
|
||||
if lists.ContainsString(rpcAddresses, address) {
|
||||
this.Fail("HTTP API地址 '" + address + "' 和 GRPC地址冲突,请修改后提交")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 证书
|
||||
var certIds = []int64{}
|
||||
if len(params.CertIdsJSON) > 0 {
|
||||
err = json.Unmarshal(params.CertIdsJSON, &certIds)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if ((httpsConfig.IsOn && len(httpsConfig.Listen) > 0) || (restHTTPSConfig.IsOn && len(httpsConfig.Listen) > 0)) && len(certIds) == 0 {
|
||||
this.Fail("请添加至少一个证书")
|
||||
}
|
||||
|
||||
var 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
|
||||
}
|
||||
|
||||
// 创建策略
|
||||
var sslPolicyId = params.SslPolicyId
|
||||
if sslPolicyId == 0 {
|
||||
if len(certIds) > 0 {
|
||||
sslPolicyCreateResp, err := this.RPC().SSLPolicyRPC().CreateSSLPolicy(this.AdminContext(), &pb.CreateSSLPolicyRequest{
|
||||
SslCertsJSON: certRefsJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
sslPolicyId = sslPolicyCreateResp.SslPolicyId
|
||||
}
|
||||
} else {
|
||||
_, err = this.RPC().SSLPolicyRPC().UpdateSSLPolicy(this.AdminContext(), &pb.UpdateSSLPolicyRequest{
|
||||
SslPolicyId: sslPolicyId,
|
||||
SslCertsJSON: certRefsJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
httpsConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
|
||||
IsOn: true,
|
||||
SSLPolicyId: sslPolicyId,
|
||||
}
|
||||
restHTTPSConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
|
||||
IsOn: true,
|
||||
SSLPolicyId: sslPolicyId,
|
||||
}
|
||||
|
||||
// 访问地址
|
||||
var accessAddrs = []*serverconfigs.NetworkAddressConfig{}
|
||||
err = json.Unmarshal(params.AccessAddrsJSON, &accessAddrs)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if len(accessAddrs) == 0 {
|
||||
this.Fail("请添加至少一个外部访问地址")
|
||||
}
|
||||
|
||||
httpJSON, err := json.Marshal(httpConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
httpsJSON, err := json.Marshal(httpsConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
restHTTPJSON, err := json.Marshal(restHTTPConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
restHTTPSJSON, err := json.Marshal(restHTTPSConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = this.RPC().APINodeRPC().UpdateAPINode(this.AdminContext(), &pb.UpdateAPINodeRequest{
|
||||
ApiNodeId: params.NodeId,
|
||||
Name: params.Name,
|
||||
Description: params.Description,
|
||||
HttpJSON: httpJSON,
|
||||
HttpsJSON: httpsJSON,
|
||||
RestIsOn: params.RestIsOn,
|
||||
RestHTTPJSON: restHTTPJSON,
|
||||
RestHTTPSJSON: restHTTPSJSON,
|
||||
AccessAddrsJSON: params.AccessAddrsJSON,
|
||||
IsOn: params.IsOn,
|
||||
IsPrimary: params.IsPrimary,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建日志
|
||||
defer this.CreateLogInfo(codes.APINode_LogUpdateAPINode, params.NodeId)
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type UpdateAddrPopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpdateAddrPopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *UpdateAddrPopupAction) RunGet(params struct{}) {
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *UpdateAddrPopupAction) RunPost(params struct {
|
||||
Protocol string
|
||||
Addr string
|
||||
Must *actions.Must
|
||||
}) {
|
||||
params.Must.
|
||||
Field("addr", params.Addr).
|
||||
Require("请输入访问地址")
|
||||
|
||||
// 兼容URL
|
||||
if regexp.MustCompile(`^(?i)(http|https)://`).MatchString(params.Addr) {
|
||||
u, err := url.Parse(params.Addr)
|
||||
if err != nil {
|
||||
this.FailField("addr", "错误的访问地址,不需要添加http://或https://")
|
||||
}
|
||||
params.Addr = u.Host
|
||||
}
|
||||
|
||||
// 自动添加端口
|
||||
if !strings.Contains(params.Addr, ":") {
|
||||
switch params.Protocol {
|
||||
case "http":
|
||||
params.Addr += ":80"
|
||||
case "https":
|
||||
params.Addr += ":443"
|
||||
}
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(params.Addr)
|
||||
if err != nil {
|
||||
this.FailField("addr", "错误的访问地址")
|
||||
}
|
||||
|
||||
addrConfig := &serverconfigs.NetworkAddressConfig{
|
||||
Protocol: serverconfigs.Protocol(params.Protocol),
|
||||
Host: host,
|
||||
PortRange: port,
|
||||
}
|
||||
this.Data["addr"] = addrConfig
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
// UpgradeCheckAction 检查升级结果
|
||||
type UpgradeCheckAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpgradeCheckAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *UpgradeCheckAction) RunPost(params struct {
|
||||
NodeId int64
|
||||
}) {
|
||||
this.Data["isOk"] = false
|
||||
|
||||
nodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{ApiNodeId: params.NodeId})
|
||||
if err != nil {
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
var node = nodeResp.ApiNode
|
||||
if node == nil || len(node.AccessAddrs) == 0 {
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
apiConfig, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
var newAPIConfig = apiConfig.Clone()
|
||||
newAPIConfig.RPCEndpoints = node.AccessAddrs
|
||||
rpcClient, err := rpc.NewRPCClient(newAPIConfig, false)
|
||||
if err != nil {
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
versionResp, err := rpcClient.APINodeRPC().FindCurrentAPINodeVersion(rpcClient.Context(0), &pb.FindCurrentAPINodeVersionRequest{})
|
||||
if err != nil {
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
if versionResp.Version != teaconst.Version {
|
||||
this.Success()
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["isOk"] = true
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/apinodeutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type UpgradePopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpgradePopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *UpgradePopupAction) RunGet(params struct {
|
||||
NodeId int64
|
||||
}) {
|
||||
this.Data["nodeId"] = params.NodeId
|
||||
this.Data["nodeName"] = ""
|
||||
this.Data["currentVersion"] = ""
|
||||
this.Data["latestVersion"] = ""
|
||||
this.Data["result"] = ""
|
||||
this.Data["resultIsOk"] = true
|
||||
this.Data["canUpgrade"] = false
|
||||
this.Data["isUpgrading"] = false
|
||||
|
||||
nodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{ApiNodeId: params.NodeId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var node = nodeResp.ApiNode
|
||||
if node == nil {
|
||||
this.Data["result"] = "要升级的节点不存在"
|
||||
this.Data["resultIsOk"] = false
|
||||
this.Show()
|
||||
return
|
||||
}
|
||||
this.Data["nodeName"] = node.Name + " / [" + strings.Join(node.AccessAddrs, ", ") + "]"
|
||||
|
||||
// 节点状态
|
||||
var status = &nodeconfigs.NodeStatus{}
|
||||
if len(node.StatusJSON) > 0 {
|
||||
err = json.Unmarshal(node.StatusJSON, &status)
|
||||
if err != nil {
|
||||
this.ErrorPage(errors.New("decode status failed: " + err.Error()))
|
||||
return
|
||||
}
|
||||
this.Data["currentVersion"] = status.BuildVersion
|
||||
} else {
|
||||
this.Data["result"] = "无法检测到节点当前版本"
|
||||
this.Data["resultIsOk"] = false
|
||||
this.Show()
|
||||
return
|
||||
}
|
||||
this.Data["latestVersion"] = teaconst.APINodeVersion
|
||||
|
||||
if status.IsActive && len(status.BuildVersion) > 0 {
|
||||
canUpgrade, reason := apinodeutils.CanUpgrade(status.BuildVersion, status.OS, status.Arch)
|
||||
if !canUpgrade {
|
||||
this.Data["result"] = reason
|
||||
this.Data["resultIsOk"] = false
|
||||
this.Show()
|
||||
return
|
||||
}
|
||||
this.Data["canUpgrade"] = true
|
||||
this.Data["result"] = "等待升级"
|
||||
this.Data["resultIsOk"] = true
|
||||
} else {
|
||||
this.Data["result"] = "当前节点非连接状态无法远程升级"
|
||||
this.Data["resultIsOk"] = false
|
||||
this.Show()
|
||||
return
|
||||
}
|
||||
|
||||
// 是否正在升级
|
||||
var oldUpgrader = apinodeutils.SharedManager.FindUpgrader(params.NodeId)
|
||||
if oldUpgrader != nil {
|
||||
this.Data["result"] = "正在升级中..."
|
||||
this.Data["resultIsOk"] = false
|
||||
this.Data["isUpgrading"] = true
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *UpgradePopupAction) RunPost(params struct {
|
||||
NodeId int64
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
var manager = apinodeutils.SharedManager
|
||||
|
||||
var oldUpgrader = manager.FindUpgrader(params.NodeId)
|
||||
if oldUpgrader != nil {
|
||||
this.Fail("正在升级中,无需重复提交 ...")
|
||||
return
|
||||
}
|
||||
|
||||
var upgrader = apinodeutils.NewUpgrader(params.NodeId)
|
||||
manager.AddUpgrader(upgrader)
|
||||
defer func() {
|
||||
manager.RemoveUpgrader(upgrader)
|
||||
}()
|
||||
|
||||
err := upgrader.Upgrade()
|
||||
if err != nil {
|
||||
this.Fail("升级失败:" + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
Reference in New Issue
Block a user