[用户节点]可以管理用户节点

This commit is contained in:
GoEdgeLab
2020-12-14 21:24:21 +08:00
parent 60c160e4c5
commit be063f9533
39 changed files with 1205 additions and 19 deletions

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env bash
env GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o edge-admin main.go

Binary file not shown.

View File

@@ -9,13 +9,13 @@ import (
"reflect"
)
var sharedUIConfig *systemconfigs.UIConfig = nil
var sharedUIConfig *systemconfigs.AdminUIConfig = nil
const (
UISettingName = "adminUIConfig"
)
func LoadUIConfig() (*systemconfigs.UIConfig, error) {
func LoadUIConfig() (*systemconfigs.AdminUIConfig, error) {
locker.Lock()
defer locker.Unlock()
@@ -24,11 +24,11 @@ func LoadUIConfig() (*systemconfigs.UIConfig, error) {
return nil, err
}
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.UIConfig)
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.AdminUIConfig)
return &v, nil
}
func UpdateUIConfig(uiConfig *systemconfigs.UIConfig) error {
func UpdateUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
locker.Lock()
defer locker.Unlock()
@@ -52,7 +52,7 @@ func UpdateUIConfig(uiConfig *systemconfigs.UIConfig) error {
return nil
}
func loadUIConfig() (*systemconfigs.UIConfig, error) {
func loadUIConfig() (*systemconfigs.AdminUIConfig, error) {
if sharedUIConfig != nil {
return sharedUIConfig, nil
}
@@ -71,7 +71,7 @@ func loadUIConfig() (*systemconfigs.UIConfig, error) {
return sharedUIConfig, nil
}
config := &systemconfigs.UIConfig{}
config := &systemconfigs.AdminUIConfig{}
err = json.Unmarshal(resp.ValueJSON, config)
if err != nil {
logs.Println("[UI_MANAGER]" + err.Error())
@@ -82,8 +82,8 @@ func loadUIConfig() (*systemconfigs.UIConfig, error) {
return sharedUIConfig, nil
}
func defaultUIConfig() *systemconfigs.UIConfig {
return &systemconfigs.UIConfig{
func defaultUIConfig() *systemconfigs.AdminUIConfig {
return &systemconfigs.AdminUIConfig{
ProductName: "GoEdge",
AdminSystemName: "GoEdge管理员系统",
ShowOpenSourceInfo: true,

View File

@@ -34,6 +34,10 @@ func (this *AdminNode) Run() {
// 检查server配置
err := this.checkServer()
if err != nil {
if err != nil {
logs.Println("[NODE]" + err.Error())
return
}
return
}
@@ -58,6 +62,7 @@ func (this *AdminNode) Run() {
// 启动API节点
this.startAPINode()
// 启动Web服务
TeaGo.NewServer(false).
AccessLog(false).
EndAll().
@@ -129,7 +134,7 @@ func (this *AdminNode) startAPINode() {
// 生成Secret
func (this *AdminNode) genSecret() string {
tmpFile := os.TempDir() + "/edge-secret.tmp"
tmpFile := os.TempDir() + "/edge-admin-secret.tmp"
data, err := ioutil.ReadFile(tmpFile)
if err == nil && len(data) == 32 {
return string(data)

View File

@@ -95,6 +95,10 @@ func (this *RPCClient) APINodeRPC() pb.APINodeServiceClient {
return pb.NewAPINodeServiceClient(this.pickConn())
}
func (this *RPCClient) UserNodeRPC() pb.UserNodeServiceClient {
return pb.NewUserNodeServiceClient(this.pickConn())
}
func (this *RPCClient) DBNodeRPC() pb.DBNodeServiceClient {
return pb.NewDBNodeServiceClient(this.pickConn())
}

View File

@@ -13,7 +13,7 @@ func init() {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)).
Helper(NewHelper()).
Helper(settingutils.NewHelper("apiNodes")).
Helper(settingutils.NewAdvancedHelper("apiNodes")).
Prefix("/api").
Get("", new(IndexAction)).
GetPost("/node/createPopup", new(node.CreatePopupAction)).

View File

@@ -12,7 +12,7 @@ func init() {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)).
Helper(new(Helper)).
Helper(settingutils.NewHelper("dbNodes")).
Helper(settingutils.NewAdvancedHelper("dbNodes")).
Prefix("/db").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).

View File

@@ -0,0 +1,16 @@
package settings
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type AdvancedAction struct {
actionutils.ParentAction
}
func (this *AdvancedAction) Init() {
this.Nav("", "", "")
}
func (this *AdvancedAction) RunGet(params struct{}) {
// 跳转到高级设置的第一个Tab
this.RedirectURL("/settings/database")
}

View File

@@ -11,7 +11,7 @@ func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)).
Helper(settingutils.NewHelper("database")).
Helper(settingutils.NewAdvancedHelper("database")).
Prefix("/settings/database").
Get("", new(IndexAction)).
GetPost("/update", new(UpdateAction)).

View File

@@ -13,6 +13,7 @@ func init() {
Helper(NewHelper()).
Prefix("/settings").
Get("", new(IndexAction)).
Get("/advanced", new(AdvancedAction)).
EndAll()
})
}

View File

@@ -0,0 +1,41 @@
package settingutils
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/actions"
)
type AdvancedHelper struct {
tab string
}
func NewAdvancedHelper(tab string) *AdvancedHelper {
return &AdvancedHelper{
tab: tab,
}
}
func (this *AdvancedHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool) {
goNext = true
action := actionPtr.Object()
// 左侧菜单
action.Data["teaMenu"] = "settings"
action.Data["teaSubMenu"] = "advanced"
// 标签栏
tabbar := actionutils.NewTabbar()
var session = action.Session()
var adminId = session.GetInt64("adminId")
if configloaders.AllowModule(adminId, configloaders.AdminModuleCodeSetting) {
tabbar.Add("数据库", "", "/settings/database", "", this.tab == "database")
tabbar.Add("API节点", "", "/api", "", this.tab == "apiNodes")
tabbar.Add("用户节点", "", "/settings/userNodes", "", this.tab == "userNodes")
tabbar.Add("日志数据库", "", "/db", "", this.tab == "dbNodes")
}
actionutils.SetTabbar(actionPtr, tabbar)
return
}

View File

@@ -32,9 +32,6 @@ func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool)
tabbar.Add("Web服务", "", "/settings/server", "", this.tab == "server")
tabbar.Add("界面设置", "", "/settings/ui", "", this.tab == "ui")
tabbar.Add("安全设置", "", "/settings/security", "", this.tab == "security")
tabbar.Add("数据库", "", "/settings/database", "", this.tab == "database")
tabbar.Add("API节点", "", "/api", "", this.tab == "apiNodes")
tabbar.Add("日志数据库", "", "/db", "", this.tab == "dbNodes")
tabbar.Add("IP库", "", "/settings/ip-library", "", this.tab == "ipLibrary")
tabbar.Add("备份", "", "/settings/backup", "", this.tab == "backup")
}

View File

@@ -0,0 +1,28 @@
package usernodes
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
NodeId int64
}) {
// TODO 检查权限
_, err := this.RPC().UserNodeRPC().DeleteUserNode(this.AdminContext(), &pb.DeleteUserNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "删除用户节点 %d", params.NodeId)
this.Success()
}

View File

@@ -0,0 +1,15 @@
package usernodes
import (
"github.com/iwind/TeaGo/actions"
)
type Helper struct {
}
func NewHelper() *Helper {
return &Helper{}
}
func (this *Helper) BeforeAction(action *actions.ActionObject) {
}

View File

@@ -0,0 +1,50 @@
package usernodes
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "index")
}
func (this *IndexAction) RunGet(params struct{}) {
countResp, err := this.RPC().UserNodeRPC().CountAllEnabledUserNodes(this.AdminContext(), &pb.CountAllEnabledUserNodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
nodeMaps := []maps.Map{}
if count > 0 {
nodesResp, err := this.RPC().UserNodeRPC().ListEnabledUserNodes(this.AdminContext(), &pb.ListEnabledUserNodesRequest{
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
for _, node := range nodesResp.Nodes {
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"isOn": node.IsOn,
"name": node.Name,
"accessAddrs": node.AccessAddrs,
})
}
}
this.Data["nodes"] = nodeMaps
this.Show()
}

View File

@@ -0,0 +1,23 @@
package usernodes
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/settingutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/user-nodes/node"
"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("userNodes")).
Prefix("/settings/userNodes").
Get("", new(IndexAction)).
GetPost("/node/createPopup", new(node.CreatePopupAction)).
Post("/delete", new(DeleteAction)).
EndAll()
})
}

View File

@@ -0,0 +1,46 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"net"
)
// 添加地址
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("请输入访问地址")
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()
}

View File

@@ -0,0 +1,143 @@
package node
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"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"
)
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
IsOn bool
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入用户节点名称")
httpConfig := &serverconfigs.HTTPProtocolConfig{}
httpsConfig := &serverconfigs.HTTPSProtocolConfig{}
// 监听地址
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)
}
}
// 证书
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 && len(certIds) == 0 {
this.Fail("请添加至少一个证书")
}
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{
CertsJSON: certRefsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
sslPolicyId := sslPolicyCreateResp.SslPolicyId
httpsConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
IsOn: true,
SSLPolicyId: sslPolicyId,
}
}
// 访问地址
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
}
createResp, err := this.RPC().UserNodeRPC().CreateUserNode(this.AdminContext(), &pb.CreateUserNodeRequest{
Name: params.Name,
Description: params.Description,
HttpJSON: httpJSON,
HttpsJSON: httpsJSON,
AccessAddrsJSON: params.AccessAddrsJSON,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "创建用户节点 %d", createResp.NodeId)
this.Success()
}

View File

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

View File

@@ -0,0 +1,102 @@
package node
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"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"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "index")
}
func (this *IndexAction) RunGet(params struct {
NodeId int64
}) {
nodeResp, err := this.RPC().UserNodeRPC().FindEnabledUserNode(this.AdminContext(), &pb.FindEnabledUserNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.Node
if node == nil {
this.NotFound("userNode", params.NodeId)
return
}
// 监听地址
httpConfig := &serverconfigs.HTTPProtocolConfig{}
if len(node.HttpJSON) > 0 {
err = json.Unmarshal(node.HttpJSON, httpConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
httpsConfig := &serverconfigs.HTTPSProtocolConfig{}
if len(node.HttpsJSON) > 0 {
err = json.Unmarshal(node.HttpsJSON, httpsConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
// 监听地址
listens := []*serverconfigs.NetworkAddressConfig{}
listens = append(listens, httpConfig.Listen...)
listens = append(listens, httpsConfig.Listen...)
// 证书信息
certs := []*sslconfigs.SSLCertConfig{}
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})
if err != nil {
this.ErrorPage(err)
return
}
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
}
}
// 访问地址
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,
"accessAddrs": accessAddrs,
"hasHTTPS": sslPolicyId > 0,
"certs": certs,
}
this.Show()
}

View File

@@ -0,0 +1,29 @@
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("userNodes")).
Prefix("/settings/userNodes/node").
// 这里不受Helper的约束
GetPost("/createAddrPopup", new(CreateAddrPopupAction)).
GetPost("/updateAddrPopup", new(UpdateAddrPopupAction)).
// 节点相关
Helper(NewHelper()).
Get("", new(IndexAction)).
GetPost("/update", new(UpdateAction)).
Get("/install", new(InstallAction)).
EndAll()
})
}

View File

@@ -0,0 +1,57 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"strings"
)
type InstallAction struct {
actionutils.ParentAction
}
func (this *InstallAction) Init() {
this.Nav("", "", "install")
}
func (this *InstallAction) RunGet(params struct {
NodeId int64
}) {
// 用户节点信息
nodeResp, err := this.RPC().UserNodeRPC().FindEnabledUserNode(this.AdminContext(), &pb.FindEnabledUserNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.Node
if node == nil {
this.NotFound("userNode", params.NodeId)
return
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"uniqueId": node.UniqueId,
"secret": node.Secret,
}
// API节点列表
apiNodesResp, err := this.RPC().APINodeRPC().FindAllEnabledAPINodes(this.AdminContext(), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
apiNodes := apiNodesResp.Nodes
apiEndpoints := []string{}
for _, apiNode := range apiNodes {
if !apiNode.IsOn {
continue
}
apiEndpoints = append(apiEndpoints, apiNode.AccessAddrs...)
}
this.Data["apiEndpoints"] = "\"" + strings.Join(apiEndpoints, "\", \"") + "\""
this.Show()
}

View File

@@ -0,0 +1,240 @@
package node
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"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/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().UserNodeRPC().FindEnabledUserNode(this.AdminContext(), &pb.FindEnabledUserNodeRequest{
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.Node
if node == nil {
this.WriteString("要操作的节点不存在")
return
}
httpConfig := &serverconfigs.HTTPProtocolConfig{}
if len(node.HttpJSON) > 0 {
err = json.Unmarshal(node.HttpJSON, httpConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
httpsConfig := &serverconfigs.HTTPSProtocolConfig{}
if len(node.HttpsJSON) > 0 {
err = json.Unmarshal(node.HttpsJSON, httpsConfig)
if err != nil {
this.ErrorPage(err)
return
}
}
// 监听地址
listens := []*serverconfigs.NetworkAddressConfig{}
listens = append(listens, httpConfig.Listen...)
listens = append(listens, httpsConfig.Listen...)
// 证书信息
certs := []*sslconfigs.SSLCertConfig{}
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})
if err != nil {
this.ErrorPage(err)
return
}
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
}
}
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,
"certs": certs,
"sslPolicyId": sslPolicyId,
"accessAddrs": accessAddrs,
}
this.Show()
}
// 保存基础设置
func (this *UpdateAction) RunPost(params struct {
NodeId int64
Name string
SslPolicyId int64
ListensJSON []byte
CertIdsJSON []byte
AccessAddrsJSON []byte
Description string
IsOn bool
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入用户节点名称")
httpConfig := &serverconfigs.HTTPProtocolConfig{}
httpsConfig := &serverconfigs.HTTPSProtocolConfig{}
// 监听地址
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)
}
}
// 证书
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 && len(certIds) == 0 {
this.Fail("请添加至少一个证书")
}
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
}
// 创建策略
sslPolicyId := params.SslPolicyId
if sslPolicyId == 0 {
if len(certIds) > 0 {
sslPolicyCreateResp, err := this.RPC().SSLPolicyRPC().CreateSSLPolicy(this.AdminContext(), &pb.CreateSSLPolicyRequest{
CertsJSON: certRefsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
sslPolicyId = sslPolicyCreateResp.SslPolicyId
}
} else {
_, err = this.RPC().SSLPolicyRPC().UpdateSSLPolicy(this.AdminContext(), &pb.UpdateSSLPolicyRequest{
SslPolicyId: sslPolicyId,
CertsJSON: certRefsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
}
httpsConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
IsOn: true,
SSLPolicyId: sslPolicyId,
}
// 访问地址
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
}
_, err = this.RPC().UserNodeRPC().UpdateUserNode(this.AdminContext(), &pb.UpdateUserNodeRequest{
NodeId: params.NodeId,
Name: params.Name,
Description: params.Description,
HttpJSON: httpJSON,
HttpsJSON: httpsJSON,
AccessAddrsJSON: params.AccessAddrsJSON,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "修改用户节点 %d", params.NodeId)
this.Success()
}

View File

@@ -0,0 +1,42 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"net"
)
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("请输入访问地址")
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()
}

View File

@@ -226,6 +226,13 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
"module": configloaders.AdminModuleCodeSetting,
"name": "系统设置",
"icon": "setting",
"subItems": []maps.Map{
{
"name": "高级设置",
"url": "/settings/advanced",
"code": "advanced",
},
},
},
}

View File

@@ -85,6 +85,7 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/server"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ui"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/upgrade"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/user-nodes"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/setup"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ui"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/users"

View File

@@ -2,7 +2,7 @@ Vue.component("network-addresses-view", {
props: ["v-addresses"],
template: `<div>
<div class="ui label tiny basic" v-if="vAddresses != null" v-for="addr in vAddresses">
{{addr.protocol}}://{{addr.host}}:{{addr.portRange}}
{{addr.protocol}}://<span v-if="addr.host.length > 0">{{addr.host}}</span><span v-else>*</span>:{{addr.portRange}}
</div>
</div>`
})

View File

@@ -0,0 +1,35 @@
{$layout}
<first-menu>
<a href="" class="item" @click.prevent="createNode()">[添加节点]</a>
</first-menu>
<p class="comment" v-if="nodes.length == 0">暂时还没有节点。</p>
<table class="ui table selectable celled" v-if="nodes.length > 0">
<thead>
<tr>
<th>节点名称</th>
<th>访问地址</th>
<th class="center width10">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="node in nodes">
<td>{{node.name}}</td>
<td>
<div v-if="node.accessAddrs != null && node.accessAddrs.length > 0">
<span class="ui label tiny basic" v-for="addr in node.accessAddrs">{{addr}}</span>
</div>
</td>
<td class="center">
<label-on :v-is-on="node.isOn"></label-on>
</td>
<td>
<a :href="'/settings/userNodes/node?nodeId=' + node.id">详情</a> &nbsp;
<a href="" @click.prevent="deleteNode(node.id)">删除</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -0,0 +1,26 @@
Tea.context(function () {
// 创建节点
this.createNode = function () {
teaweb.popup("/settings/userNodes/node/createPopup", {
width: "50em",
height: "30em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
// 删除节点
this.deleteNode = function (nodeId) {
let that = this
teaweb.confirm("确定要删除此节点吗?", function () {
that.$post("/settings/userNodes/delete")
.params({
nodeId: nodeId
})
.refresh()
})
}
})

View File

@@ -0,0 +1,7 @@
<first-menu>
<menu-item href="/settings/userNodes">节点列表</menu-item>
<span class="item">|</span>
<menu-item :href="'/settings/userNodes/node?nodeId=' + node.id" code="index">"{{node.name}}"详情</menu-item>
<menu-item :href="'/settings/userNodes/node/install?nodeId=' + node.id" code="install">安装节点</menu-item>
<menu-item :href="'/settings/userNodes/node/update?nodeId=' + node.id" code="update">修改节点</menu-item>
</first-menu>

View File

@@ -0,0 +1,24 @@
{$layout "layout_popup"}
<h3>添加访问地址</h3>
<form class="ui form" method="post" data-tea-action="$" data-tea-success="success">
<table class="ui table definition selectable">
<tr>
<td>网络协议</td>
<td>
<select class="ui dropdown auto-width" name="protocol">
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</select>
</td>
</tr>
<tr>
<td class="title">访问地址</td>
<td>
<input type="text" name="addr" maxlength="100" ref="focus"/>
<p class="comment">可以是"IP:端口"或者"域名:端口"。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,55 @@
{$layout "layout_popup"}
<h3>添加用户节点</h3>
<form class="ui form" method="post" data-tea-action="$" data-tea-success="success">
<table class="ui table selectable definition">
<tr>
<td class="title">节点名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus"/>
</td>
</tr>
<tr>
<td>进程监听端口 *</td>
<td>
<network-addresses-box :v-name="'listensJSON'" :v-server-type="'httpWeb'" @change="changeListens"></network-addresses-box>
<p class="comment">用户节点进程监听的网络端口。</p>
</td>
</tr>
<tr v-if="hasHTTPS">
<td>HTTPS证书 *</td>
<td>
<ssl-certs-box :v-protocol="'https'"></ssl-certs-box>
</td>
</tr>
<tr>
<td>外部访问地址 *</td>
<td>
<api-node-addresses-box :v-name="'accessAddrsJSON'"></api-node-addresses-box>
<p class="comment">可以公开访问的网络地址。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>描述</td>
<td>
<textarea name="description" maxlength="200" rows="3"></textarea>
</td>
</tr>
<tr>
<td>是否启用</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" checked="checked"/>
<label></label>
</div>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,8 @@
Tea.context(function () {
this.hasHTTPS = false
this.changeListens = function (addrs) {
this.hasHTTPS = addrs.$any(function (k, v) {
return v.protocol == "https"
})
}
})

View File

@@ -0,0 +1,44 @@
{$layout}
{$template "menu"}
<table class="ui table selectable definition">
<tr>
<td class="title">节点名称</td>
<td>
{{node.name}}
</td>
</tr>
<tr>
<td>状态</td>
<td>
<label-on :v-is-on="node.isOn"></label-on>
</td>
</tr>
<tr>
<td>进程监听端口</td>
<td>
<network-addresses-view :v-addresses="node.listens"></network-addresses-view>
<p class="comment">用户节点进程监听的网络端口。</p>
</td>
</tr>
<tr v-if="node.hasHTTPS">
<td>HTTPS证书</td>
<td>
<ssl-certs-view :v-certs="node.certs"></ssl-certs-view>
</td>
</tr>
<tr>
<td>外部访问地址</td>
<td>
<network-addresses-view :v-addresses="node.accessAddrs"></network-addresses-view>
<p class="comment">可以公开访问的网络地址。</p>
</td>
</tr>
<tr>
<td>描述</td>
<td>
<span v-if="node.description.length > 0">{{node.description}}</span>
<span v-else class="disabled">暂时还没有描述。</span>
</td>
</tr>
</table>

View File

@@ -0,0 +1,23 @@
{$layout}
{$template "menu"}
<h3>安装步骤</h3>
<ol class="ui list">
<li>按照下面的配置信息替换<code-label>configs/api.yaml</code-label>内容</li>
<li>使用<code-label>bin/edge-user start</code-label>启动节点</li>
<li>可以在<code-label>logs/run.log</code-label>中查看启动是否有异常</li>
</ol>
<div class="ui divider"></div>
<h3>配置信息</h3>
<table class="ui table definition selectable">
<tr>
<td class="title">configs/api.yaml<em><br/><download-link :v-element="'api-code'" :v-file="'api.yaml'">[下载]</download-link></em></td>
<td>
<pre id="api-code">rpc:
endpoints: [ {{apiEndpoints}} ]
nodeId: "{{node.uniqueId}}"
secret: "{{node.secret}}"</pre>
</td>
</tr>
</table>

View File

@@ -0,0 +1,58 @@
{$layout}
{$template "menu"}
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="nodeId" :value="node.id"/>
<input type="hidden" name="sslPolicyId" :value="node.sslPolicyId"/>
<table class="ui table selectable definition">
<tr>
<td class="title">节点名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus" v-model="node.name"/>
</td>
</tr>
<tr>
<td>进程监听端口 *</td>
<td>
<network-addresses-box :v-name="'listensJSON'" :v-server-type="'httpWeb'" :v-addresses="node.listens" @change="changeListens"></network-addresses-box>
<p class="comment">API节点进程监听的网络端口。</p>
</td>
</tr>
<tr v-if="hasHTTPS">
<td>HTTPS证书 *</td>
<td>
<ssl-certs-box :v-certs="node.certs" :v-protocol="'https'"></ssl-certs-box>
</td>
</tr>
<tr>
<td>外部访问地址 *</td>
<td>
<api-node-addresses-box :v-name="'accessAddrsJSON'" :v-addrs="node.accessAddrs"></api-node-addresses-box>
<p class="comment">边缘节点和管理平台等外部节点访问API节点的网络地址。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>描述</td>
<td>
<textarea name="description" maxlength="200" rows="3" v-model="node.description"></textarea>
</td>
</tr>
<tr>
<td>是否启用</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="node.isOn"/>
<label></label>
</div>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,12 @@
Tea.context(function () {
this.success = NotifySuccess("保存成功", "/settings/userNodes/node?nodeId=" + this.node.id)
this.hasHTTPS = this.node.listens.$any(function (k, v) {
return v.protocol == "https"
})
this.changeListens = function (addrs) {
this.hasHTTPS = addrs.$any(function (k, v) {
return v.protocol == "https"
})
}
})

View File

@@ -0,0 +1,24 @@
{$layout "layout_popup"}
<h3>修改访问地址</h3>
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<table class="ui table definition selectable">
<tr>
<td>网络协议</td>
<td>
<select class="ui dropdown auto-width" name="protocol" v-model="protocol">
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</select>
</td>
</tr>
<tr>
<td class="title">访问地址</td>
<td>
<input type="text" name="addr" maxlength="100" ref="focus" v-model="addr"/>
<p class="comment">可以是"IP:端口"或者"域名:端口"。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,5 @@
Tea.context(function () {
let addr = window.parent.UPDATING_ADDR
this.protocol = addr.protocol
this.addr = addr.host + ":" + addr.portRange
})