实现节点分组管理

This commit is contained in:
GoEdgeLab
2020-10-28 18:21:11 +08:00
parent 4c8ea8953b
commit 7aa62ea822
18 changed files with 354 additions and 2 deletions

View File

@@ -82,6 +82,10 @@ func (this *RPCClient) NodeClusterRPC() pb.NodeClusterServiceClient {
return pb.NewNodeClusterServiceClient(this.pickConn()) return pb.NewNodeClusterServiceClient(this.pickConn())
} }
func (this *RPCClient) NodeGroupRPC() pb.NodeGroupServiceClient {
return pb.NewNodeGroupServiceClient(this.pickConn())
}
func (this *RPCClient) NodeIPAddressRPC() pb.NodeIPAddressServiceClient { func (this *RPCClient) NodeIPAddressRPC() pb.NodeIPAddressServiceClient {
return pb.NewNodeIPAddressServiceClient(this.pickConn()) return pb.NewNodeIPAddressServiceClient(this.pickConn())
} }

View File

@@ -0,0 +1,44 @@
package groups
import (
"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("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
ClusterId int64
Name string
Must *actions.Must
}) {
if params.ClusterId <= 0 {
this.Fail("请选择集群")
}
params.Must.
Field("name", params.Name).
Require("请输入分组名称")
_, err := this.RPC().NodeGroupRPC().CreateNodeGroup(this.AdminContext(), &pb.CreateNodeGroupRequest{
ClusterId: params.ClusterId,
Name: params.Name,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,33 @@
package groups
import (
"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 {
GroupId int64
}) {
// 检查是否正在使用
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesWithGroupId(this.AdminContext(), &pb.CountAllEnabledNodesWithGroupIdRequest{GroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
if countResp.Count > 0 {
this.Fail("此分组正在被使用不能删除,请修改节点后再删除")
}
_, err = this.RPC().NodeGroupRPC().DeleteNodeGroup(this.AdminContext(), &pb.DeleteNodeGroupRequest{GroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,47 @@
package groups
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", "group")
this.SecondMenu("nodes")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
}) {
groupsResp, err := this.RPC().NodeGroupRPC().FindAllEnabledNodeGroupsWithClusterId(this.AdminContext(), &pb.FindAllEnabledNodeGroupsWithClusterIdRequest{
ClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
groupMaps := []maps.Map{}
for _, group := range groupsResp.Groups {
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesWithGroupId(this.AdminContext(), &pb.CountAllEnabledNodesWithGroupIdRequest{GroupId: group.Id})
if err != nil {
this.ErrorPage(err)
return
}
countNodes := countResp.Count
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": group.Name,
"countNodes": countNodes,
})
}
this.Data["groups"] = groupMaps
this.Show()
}

View File

@@ -0,0 +1,22 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type SortAction struct {
actionutils.ParentAction
}
func (this *SortAction) RunPost(params struct {
GroupIds []int64
}) {
_, err := this.RPC().NodeGroupRPC().UpdateNodeGroupOrders(this.AdminContext(), &pb.UpdateNodeGroupOrdersRequest{GroupIds: params.GroupIds})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,59 @@
package groups
import (
"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 UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
GroupId int64
}) {
groupResp, err := this.RPC().NodeGroupRPC().FindEnabledNodeGroup(this.AdminContext(), &pb.FindEnabledNodeGroupRequest{GroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
group := groupResp.Group
if group == nil {
this.NotFound("nodeGroup", params.GroupId)
return
}
this.Data["group"] = maps.Map{
"id": group.Id,
"name": group.Name,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
GroupId int64
Name string
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入分组名称")
_, err := this.RPC().NodeGroupRPC().UpdateNodeGroup(this.AdminContext(), &pb.UpdateNodeGroupRequest{
GroupId: params.GroupId,
Name: params.Name,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -25,14 +25,17 @@ func (this *IndexAction) RunGet(params struct {
ClusterId int64 ClusterId int64
InstalledState int InstalledState int
ActiveState int ActiveState int
Keyword string
}) { }) {
this.Data["installState"] = params.InstalledState this.Data["installState"] = params.InstalledState
this.Data["activeState"] = params.ActiveState this.Data["activeState"] = params.ActiveState
this.Data["keyword"] = params.Keyword
countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{ countResp, err := this.RPC().NodeRPC().CountAllEnabledNodesMatch(this.AdminContext(), &pb.CountAllEnabledNodesMatchRequest{
ClusterId: params.ClusterId, ClusterId: params.ClusterId,
InstallState: types.Int32(params.InstalledState), InstallState: types.Int32(params.InstalledState),
ActiveState: types.Int32(params.ActiveState), ActiveState: types.Int32(params.ActiveState),
Keyword: params.Keyword,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
@@ -48,6 +51,7 @@ func (this *IndexAction) RunGet(params struct {
ClusterId: params.ClusterId, ClusterId: params.ClusterId,
InstallState: types.Int32(params.InstalledState), InstallState: types.Int32(params.InstalledState),
ActiveState: types.Int32(params.ActiveState), ActiveState: types.Int32(params.ActiveState),
Keyword: params.Keyword,
}) })
nodeMaps := []maps.Map{} nodeMaps := []maps.Map{}
for _, node := range nodesResp.Nodes { for _, node := range nodesResp.Nodes {

View File

@@ -1,6 +1,7 @@
package cluster package cluster
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/groups"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node"
clusters "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils" clusters "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/clusterutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers" "github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
@@ -34,6 +35,14 @@ func init() {
Get("/node/logs", new(node.LogsAction)). Get("/node/logs", new(node.LogsAction)).
Post("/node/start", new(node.StartAction)). Post("/node/start", new(node.StartAction)).
Post("/node/stop", new(node.StopAction)). Post("/node/stop", new(node.StopAction)).
// 分组相关
Get("/groups", new(groups.IndexAction)).
GetPost("/groups/createPopup", new(groups.CreatePopupAction)).
GetPost("/groups/updatePopup", new(groups.UpdatePopupAction)).
Post("/groups/delete", new(groups.DeleteAction)).
Post("/groups/sort", new(groups.SortAction)).
EndAll() EndAll()
}) })
} }

View File

@@ -13,7 +13,8 @@ type UpgradeRemoteAction struct {
} }
func (this *UpgradeRemoteAction) Init() { func (this *UpgradeRemoteAction) Init() {
this.Nav("", "", "") this.Nav("", "node", "install")
this.SecondMenu("nodes")
} }
func (this *UpgradeRemoteAction) RunGet(params struct { func (this *UpgradeRemoteAction) RunGet(params struct {

View File

@@ -2,4 +2,5 @@
<menu-item :href="'/clusters/cluster?clusterId=' + clusterId" code="index">节点列表</menu-item> <menu-item :href="'/clusters/cluster?clusterId=' + clusterId" code="index">节点列表</menu-item>
<menu-item :href="'/clusters/cluster/createNode?clusterId=' + clusterId" code="create">创建节点</menu-item> <menu-item :href="'/clusters/cluster/createNode?clusterId=' + clusterId" code="create">创建节点</menu-item>
<menu-item :href="'/clusters/cluster/installManual?clusterId=' + clusterId" code="install">安装升级</menu-item> <menu-item :href="'/clusters/cluster/installManual?clusterId=' + clusterId" code="install">安装升级</menu-item>
<menu-item :href="'/clusters/cluster/groups?clusterId=' + clusterId" code="group">节点分组</menu-item>
</second-menu> </second-menu>

View File

@@ -0,0 +1,15 @@
{$layout "layout_popup"}
<h3>创建分组</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="clusterId" :value="clusterId"/>
<table class="ui table definition selectable">
<tr>
<td>分组名称 *</td>
<td>
<input type="text" name="name" maxlength="50" ref="focus"/>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,3 @@
Tea.context(function () {
this.success = NotifyPopup
})

View File

@@ -0,0 +1,36 @@
{$layout}
{$template "../menu"}
<first-menu style="margin-top:-1em">
<a href="" class="item" @click.prevent="createGroup()">[创建分组]</a>
</first-menu>
<p class="comment" v-if="groups.length == 0">暂时还没有分组。</p>
<div v-show="groups.length > 0">
<div class="margin"></div>
<table class="ui table selectable" id="sortable-table">
<thead>
<tr>
<th style="width:3em"></th>
<th>分组名称</th>
<th>节点数</th>
<th class="two op">操作</th>
</tr>
</thead>
<tbody v-for="group in groups" :data-group-id="group.id">
<tr>
<td style="text-align: center;"><i class="icon bars handle grey"></i> </td>
<td>{{group.name}}</td>
<td>
<span v-if="group.countNodes.length > 0">{{group.countNodes}}</span>
<span v-else class="disabled">0</span>
</td>
<td>
<a href="" @click.prevent="updateGroup(group.id)">修改</a> &nbsp; <a href="" @click.prevent="deleteGroup(group.id)">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
<p v-if="groups.length > 0" class="comment">可以拖动左侧的<i class="icon bars"></i>排序。</p>

View File

@@ -0,0 +1,53 @@
Tea.context(function () {
this.$delay(function () {
let that = this
sortTable(function () {
let groupIds = []
document.querySelectorAll("*[data-group-id]").forEach(function (element) {
groupIds.push(element.getAttribute("data-group-id"))
})
that.$post("/clusters/cluster/groups/sort")
.params({
groupIds: groupIds
})
.success(function () {
teaweb.successToast("保存成功")
})
})
})
this.createGroup = function () {
teaweb.popup("/clusters/cluster/groups/createPopup?clusterId=" + this.clusterId, {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.updateGroup = function (groupId) {
teaweb.popup("/clusters/cluster/groups/updatePopup?groupId=" + groupId, {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deleteGroup = function (groupId) {
let that = this
teaweb.confirm("确定要删除这个分组吗?", function () {
that.$post("/clusters/cluster/groups/delete")
.params({
groupId: groupId
})
.success(function () {
teaweb.success("删除成功", function () {
teaweb.reload()
})
})
})
}
})

View File

@@ -0,0 +1,15 @@
{$layout "layout_popup"}
<h3>修改分组</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="groupId" :value="group.id"/>
<table class="ui table definition selectable">
<tr>
<td>分组名称 *</td>
<td>
<input type="text" name="name" maxlength="50" ref="focus" v-model="group.name"/>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,3 @@
Tea.context(function () {
this.success = NotifyPopup
})

View File

@@ -25,6 +25,9 @@
<option value="2">不在线</option> <option value="2">不在线</option>
</select> </select>
</div> </div>
<div class="ui field">
<input type="text" name="keyword" placeholder="关键词" v-model="keyword" style="width:10em"/>
</div>
<div class="ui field"> <div class="ui field">
<button class="ui button" type="submit">搜索</button> <button class="ui button" type="submit">搜索</button>
</div> </div>

View File

@@ -10,7 +10,7 @@
<!-- 未安装 --> <!-- 未安装 -->
<div v-if="!node.isInstalled"> <div v-if="!node.isInstalled">
<h3>方法1自动安装</h3> <h3>方法1通过SSH自动安装</h3>
<div v-if="installStatus != null && (installStatus.isRunning || installStatus.isFinished)" <div v-if="installStatus != null && (installStatus.isRunning || installStatus.isFinished)"
class="ui segment installing-box"> class="ui segment installing-box">