pull/45/head
Doflatango 2017-10-18 14:44:51 +08:00
parent 0104aa1bd6
commit 002d565e17
8 changed files with 98 additions and 28 deletions

View File

@ -90,6 +90,13 @@ func GetNodesBy(query interface{}) (nodes []*Node, err error) {
return return
} }
func RemoveNode(query interface{}) error {
return mgoDB.WithC(Coll_Node, func(c *mgo.Collection) error {
return c.Remove(query)
})
}
func ISNodeFault(id string) (bool, error) { func ISNodeFault(id string) (bool, error) {
n := 0 n := 0
err := mgoDB.WithC(Coll_Node, func(c *mgo.Collection) error { err := mgoDB.WithC(Coll_Node, func(c *mgo.Collection) error {

0
web/gen_bindata.sh Normal file → Executable file
View File

View File

@ -8,6 +8,7 @@ import (
v3 "github.com/coreos/etcd/clientv3" v3 "github.com/coreos/etcd/clientv3"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"gopkg.in/mgo.v2/bson"
"github.com/shunfei/cronsun" "github.com/shunfei/cronsun"
"github.com/shunfei/cronsun/conf" "github.com/shunfei/cronsun/conf"
@ -163,3 +164,32 @@ func (n *Node) GetNodes(ctx *Context) {
outJSONWithCode(ctx.W, http.StatusOK, nodes) outJSONWithCode(ctx.W, http.StatusOK, nodes)
} }
// DeleteNode force remove node (by ip) which state in offline or damaged.
func (n *Node) DeleteNode(ctx *Context) {
vars := mux.Vars(ctx.R)
ip := strings.TrimSpace(vars["ip"])
if len(ip) == 0 {
outJSONWithCode(ctx.W, http.StatusBadRequest, "node ip is required.")
return
}
resp, err := cronsun.DefalutClient.Get(conf.Config.Node + ip)
if err != nil {
outJSONWithCode(ctx.W, http.StatusInternalServerError, err.Error())
return
}
if len(resp.Kvs) > 0 {
outJSONWithCode(ctx.W, http.StatusBadRequest, "can not remove a running node.")
return
}
err = cronsun.RemoveNode(bson.M{"_id": ip})
if err != nil {
outJSONWithCode(ctx.W, http.StatusInternalServerError, err.Error())
return
}
outJSONWithCode(ctx.W, http.StatusNoContent, nil)
}

View File

@ -82,6 +82,8 @@ func initRouters() (s *http.Server, err error) {
h = NewAuthHandler(nodeHandler.GetNodes) h = NewAuthHandler(nodeHandler.GetNodes)
subrouter.Handle("/nodes", h).Methods("GET") subrouter.Handle("/nodes", h).Methods("GET")
h = NewAuthHandler(nodeHandler.DeleteNode)
subrouter.Handle("/node/{ip}", h).Methods("DELETE")
// get node group list // get node group list
h = NewAuthHandler(nodeHandler.GetGroups) h = NewAuthHandler(nodeHandler.GetGroups)
subrouter.Handle("/node/groups", h).Methods("GET") subrouter.Handle("/node/groups", h).Methods("GET")

File diff suppressed because one or more lines are too long

View File

@ -1,32 +1,50 @@
<style scoped> <style scoped>
.node { .node {
width: 130px; width: 140px;
border-radius: 3px; border-radius: 3px;
padding: 4px 0;
margin: 3px; margin: 3px;
display: inline-block; display: inline-block;
background: #e8e8e8; background: #e8e8e8;
text-align: center; text-align: center;
position: relative;
overflow: hidden;
line-height: 1.9em;
} }
.node > i.icon.remove {
position: absolute;
top: 0;
right: 0;
background: #2185D0;
bottom: 0;
display: none;
color: white;
margin: 0;
height: auto;
width: 100%;
cursor: pointer;
}
.node:hover > i.icon.remove {display: block;}
</style> </style>
<template> <template>
<div> <div>
<div class="clearfix"> <div class="clearfix">
<router-link class="ui right floated primary button" to="/node/group"><i class="cubes icon"></i> {{$L('group manager')}}</router-link> <router-link class="ui right floated primary button" to="/node/group"><i class="cubes icon"></i> {{$L('group manager')}}</router-link>
<div class="ui label" <div class="ui label"
<div class="ui label" v-for="item in items" v-bind:title="$L(item.title)"> <div class="ui label" v-for="group in groups" v-bind:title="$L(group.title)">
<i class="cube icon" v-bind:class="item.css"></i> {{item.nodes.length}} {{$L(item.name)}} <i class="cube icon" v-bind:class="group.css"></i> {{group.nodes.length}} {{$L(group.name)}}
</div> </div>
{{$L('(total {n} nodes)', count)}} {{$L('(total {n} nodes)', count)}}
<div class="ui label" :title="$L('currently version')"> {{version}} </div> <div class="ui label" :title="$L('currently version')"> {{version}} </div>
</div> </div>
<div class="ui relaxed list" v-for="item in items"> <div class="ui relaxed list" v-for="(group, groupIndex) in groups">
<h4 v-if="item.nodes.length > 0" class="ui horizontal divider header"><i class="cube icon" v-bind:class="item.css"></i> {{$L(item.name)}} {{item.nodes.length}}</h4> <h4 v-if="group.nodes.length > 0" class="ui horizontal divider header"><i class="cube icon" v-bind:class="group.css"></i> {{$L(group.name)}} {{group.nodes.length}}</h4>
<div v-for="node in item.nodes" class="node" v-bind:title="node.title"> <div v-for="(node, nodeIndex) in group.nodes" class="node" v-bind:title="node.title">
<router-link class="item" :to="'/job?node='+node.id"> <router-link class="item" :to="'/job?node='+node.id">
<i class="red icon fork" v-if="node.version !== version" :title="$L('version inconsistent, node: {version}', node.version)"></i> <i class="red icon fork" v-if="node.version !== version" :title="$L('version inconsistent, node: {version}', node.version)"></i>
{{node.id}} {{node.id}}
</router-link> </router-link>
<i v-if="groupIndex != 2" v-on:click="removeConfirm(groupIndex, nodeIndex, node.id)" class="icon remove"></i>
</div> </div>
</div> </div>
</div> </div>
@ -37,10 +55,10 @@ export default {
name: 'node', name: 'node',
data: function(){ data: function(){
return { return {
items: [ groups: [
{nodes:[],name:'node damaged',title:'node can not be deceted due to itself or network etc.',css:'red'}, {nodes: [], name: 'node damaged', title: 'node can not be deceted due to itself or network etc.', css:'red'},
{nodes:[],name:'node offline',title:'node is in maintenance or is shutdown manually',css:''}, {nodes: [], name: 'node offline', title: 'node is in maintenance or is shutdown manually', css:''},
{nodes:[],name:'node normaly',title:'node is running',css:'green'} {nodes: [], name: 'node normaly', title: 'node is running', css:'green'}
], ],
count: 0, count: 0,
version: '' version: ''
@ -67,15 +85,26 @@ export default {
var n = resp[i]; var n = resp[i];
n.title = n.version + "\nstarted at: " + n.up n.title = n.version + "\nstarted at: " + n.up
if (n.alived && n.connected) { if (n.alived && n.connected) {
vm.items[2].nodes.push(n); vm.groups[2].nodes.push(n);
} else if (n.alived && !n.connected) { } else if (n.alived && !n.connected) {
vm.items[0].nodes.push(n); vm.groups[0].nodes.push(n);
} else { } else {
vm.items[1].nodes.push(n); vm.groups[1].nodes.push(n);
} }
} }
vm.count = resp.length || 0; vm.count = resp.length || 0;
}).do(); }).do();
},
methods: {
removeConfirm: function(groupIndex, nodeIndex, nodeId){
if (!confirm(this.$L('are you sure to remove the node {nodeId}?', nodeId))) return;
var vm = this;
this.$rest.DELETE('node/'+nodeId).onsucceed(204, (resp)=>{
vm.groups[groupIndex].nodes.splice(nodeIndex, 1);
}).do();
}
} }
} }
</script> </script>

View File

@ -129,7 +129,8 @@ var language = {
'include nodes': 'Include nodes', 'include nodes': 'Include nodes',
'select nodes': 'Select nodes', 'select nodes': 'Select nodes',
'select groups': 'Select groups', 'select groups': 'Select groups',
'are you sure to delete the group {name}?': 'Are you sure to delete the group {0}?' 'are you sure to delete the group {name}?': 'Are you sure to delete the group {0}?',
'are you sure to remove the node {nodeId}?': 'Are you sure to remove the node {0}?'
} }
export default language; export default language;

View File

@ -131,7 +131,8 @@ var language = {
'include nodes': '包含的节点', 'include nodes': '包含的节点',
'select nodes': '选择节点', 'select nodes': '选择节点',
'select groups': '选择分组', 'select groups': '选择分组',
'are you sure to delete the group {name}?': '确定删除分组 {0}?' 'are you sure to delete the group {name}?': '确定删除分组 {0}?',
'are you sure to remove the node {nodeId}?': '确定删除节点 {0}?'
} }
export default language; export default language;