增加认证节点管理

This commit is contained in:
GoEdgeLab
2021-04-13 21:23:13 +08:00
parent d1d50c7860
commit 53968dd313
24 changed files with 640 additions and 1 deletions

View File

@@ -332,6 +332,10 @@ func (this *RPCClient) AuthorityKeyRPC() pb.AuthorityKeyServiceClient {
return pb.NewAuthorityKeyServiceClient(this.pickConn())
}
func (this *RPCClient) AuthorityNodeRPC() pb.AuthorityNodeServiceClient {
return pb.NewAuthorityNodeServiceClient(this.pickConn())
}
// Context 构造Admin上下文
func (this *RPCClient) Context(adminId int64) context.Context {
ctx := context.Background()

View File

@@ -15,7 +15,7 @@ type IndexAction struct {
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
this.Nav("", "", "index")
}
func (this *IndexAction) RunGet(params struct{}) {
@@ -47,5 +47,23 @@ func (this *IndexAction) RunGet(params struct{}) {
}
this.Data["key"] = keyMap
// 检查是否有认证节点,如果没有认证节点,则自动生成一个
countResp, err := this.RPC().AuthorityNodeRPC().CountAllEnabledAuthorityNodes(this.AdminContext(), &pb.CountAllEnabledAuthorityNodesRequest{})
if err != nil {
this.ErrorPage(err)
return
}
if countResp.Count == 0 {
_, err = this.RPC().AuthorityNodeRPC().CreateAuthorityNode(this.AdminContext(), &pb.CreateAuthorityNodeRequest{
Name: "默认节点",
Description: "系统自动生成的默认节点",
IsOn: true,
})
if err != nil {
this.ErrorPage(err)
return
}
}
this.Show()
}

View File

@@ -0,0 +1,28 @@
package nodes
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().AuthorityNodeRPC().DeleteAuthorityNode(this.AdminContext(), &pb.DeleteAuthorityNodeRequest{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 nodes
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,75 @@
package nodes
import (
"encoding/json"
"fmt"
"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/logs"
"github.com/iwind/TeaGo/maps"
"time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "node")
}
func (this *IndexAction) RunGet(params struct{}) {
countResp, err := this.RPC().AuthorityNodeRPC().CountAllEnabledAuthorityNodes(this.AdminContext(), &pb.CountAllEnabledAuthorityNodesRequest{})
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().AuthorityNodeRPC().ListEnabledAuthorityNodes(this.AdminContext(), &pb.ListEnabledAuthorityNodesRequest{
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
for _, node := range nodesResp.Nodes {
// 状态
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秒之内认为活跃
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"isOn": node.IsOn,
"name": node.Name,
"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,
},
})
}
}
this.Data["nodes"] = nodeMaps
this.Show()
}

View File

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

View File

@@ -0,0 +1,47 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "node", "create")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
Description string
IsOn bool
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入认证节点名称")
createResp, err := this.RPC().AuthorityNodeRPC().CreateAuthorityNode(this.AdminContext(), &pb.CreateAuthorityNodeRequest{
Name: params.Name,
Description: params.Description,
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,39 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "index")
}
func (this *IndexAction) RunGet(params struct {
NodeId int64
}) {
nodeResp, err := this.RPC().AuthorityNodeRPC().FindEnabledAuthorityNode(this.AdminContext(), &pb.FindEnabledAuthorityNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.Node
if node == nil {
this.NotFound("authorityNode", params.NodeId)
return
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"description": node.Description,
"isOn": node.IsOn,
}
this.Show()
}

View File

@@ -0,0 +1,25 @@
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("authority")).
Prefix("/settings/authority/nodes/node").
// 节点相关
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().AuthorityNodeRPC().FindEnabledAuthorityNode(this.AdminContext(), &pb.FindEnabledAuthorityNodeRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.Node
if node == nil {
this.NotFound("authorityNode", 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,73 @@
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdateAction struct {
actionutils.ParentAction
}
func (this *UpdateAction) Init() {
this.Nav("", "", "update")
}
func (this *UpdateAction) RunGet(params struct {
NodeId int64
}) {
nodeResp, err := this.RPC().AuthorityNodeRPC().FindEnabledAuthorityNode(this.AdminContext(), &pb.FindEnabledAuthorityNodeRequest{
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
node := nodeResp.Node
if node == nil {
this.WriteString("要操作的节点不存在")
return
}
this.Data["node"] = maps.Map{
"id": node.Id,
"name": node.Name,
"description": node.Description,
"isOn": node.IsOn,
}
this.Show()
}
// 保存基础设置
func (this *UpdateAction) RunPost(params struct {
NodeId int64
Name string
Description string
IsOn bool
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入认证节点名称")
_, err := this.RPC().AuthorityNodeRPC().UpdateAuthorityNode(this.AdminContext(), &pb.UpdateAuthorityNodeRequest{
NodeId: params.NodeId,
Name: params.Name,
Description: params.Description,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "修改认证节点 %d", params.NodeId)
this.Success()
}

View File

@@ -85,6 +85,8 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/stat"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/authority"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/authority/nodes"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/authority/nodes/node"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/backup"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/database"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/ip-library"

View File

@@ -0,0 +1,4 @@
<first-menu>
<menu-item href="/settings/authority" code="index">认证信息</menu-item>
<menu-item href="/settings/authority/nodes" code="node">认证节点</menu-item>
</first-menu>

View File

@@ -1,4 +1,5 @@
{$layout}
{$template "menu"}
<div class="ui message warning" v-if="key == null">认证之后,可以使用企业版功能,当前你还没有认证,<a href="http://edge.teaos.cn/docs/Plus/Authority/Index.md" target="_blank">[在这里看认证方法]</a></div>

View File

@@ -0,0 +1,50 @@
{$layout}
{$template "../menu"}
<!--<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 class="width6 center">版本号</th>
<th class="width5 center">CPU</th>
<th class="width5 center">内存</th>
<th class="center width10">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="node in nodes">
<td>{{node.name}}</td>
<td class="center">
<span v-if="node.status.buildVersion.length > 0">v{{node.status.buildVersion}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">
<span v-if="node.status.isActive" :class="{red:node.status.cpuUsage > 0.80}">{{node.status.cpuUsageText}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">
<span v-if="node.status.isActive" :class="{red:node.status.memUsage > 0.80}">{{node.status.memUsageText}}</span>
<span v-else class="disabled">-</span>
</td>
<td class="center">
<span v-if="!node.isOn"><label-on :v-is-on="node.isOn"></label-on></span>
<div v-else-if="node.status.isActive">
<span class="green">运行中</span>
</div>
<span v-else-if="node.status.updatedAt > 0" class="red">已断开</span>
<span v-else-if="node.status.updatedAt == 0" class="red">未连接</span>
</td>
<td>
<a :href="'/settings/authority/nodes/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/authority/nodes/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/authority/nodes/delete")
.params({
nodeId: nodeId
})
.refresh()
})
}
})

View File

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

View File

@@ -0,0 +1,34 @@
{$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 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,3 @@
Tea.context(function () {
})

View File

@@ -0,0 +1,24 @@
{$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>
<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-authority 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,37 @@
{$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 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,3 @@
Tea.context(function () {
this.success = NotifySuccess("保存成功", "/settings/authority/nodes/node?nodeId=" + this.node.id)
})