mirror of https://github.com/shunfei/cronsun
use machine id instead of ip
parent
2f94f1dd9c
commit
9bbd94698b
|
@ -8,3 +8,4 @@ web/ui/node_modules
|
|||
web/ui/dist
|
||||
.vscode
|
||||
*npm-debug.log
|
||||
vendor
|
||||
|
|
|
@ -33,38 +33,39 @@ var UpgradeCmd = &cobra.Command{
|
|||
|
||||
if prever < "0.3.0" {
|
||||
fmt.Println("upgrading data to version 0.3.0")
|
||||
ipMapper := getIPMapper(ea)
|
||||
if to_0_3_0(ea, ipMapper) {
|
||||
nodesById := getIPMapper(ea)
|
||||
if to_0_3_0(ea, nodesById) {
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func getIPMapper(ea *ExitAction) map[string]string {
|
||||
func getIPMapper(ea *ExitAction) map[string]*cronsun.Node {
|
||||
nodes, err := cronsun.GetNodes()
|
||||
if err != nil {
|
||||
ea.Exit("failed to fetch nodes from MongoDB: %s", err.Error())
|
||||
}
|
||||
|
||||
var ipMapper = make(map[string]string, len(nodes))
|
||||
var ipMapper = make(map[string]*cronsun.Node, len(nodes))
|
||||
for _, n := range nodes {
|
||||
n.IP = strings.TrimSpace(n.IP)
|
||||
if n.IP == "" || n.ID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
ipMapper[n.IP] = n.ID
|
||||
ipMapper[n.IP] = n
|
||||
}
|
||||
|
||||
return ipMapper
|
||||
}
|
||||
|
||||
func to_0_3_0(ea *ExitAction, ipMapper map[string]string) (shouldStop bool) {
|
||||
// to_0_3_0 can be run many times
|
||||
func to_0_3_0(ea *ExitAction, nodesById map[string]*cronsun.Node) (shouldStop bool) {
|
||||
var replaceIDs = func(list []string) {
|
||||
for i := range list {
|
||||
if machineID, ok := ipMapper[list[i]]; ok {
|
||||
list[i] = machineID
|
||||
if node, ok := nodesById[list[i]]; ok {
|
||||
list[i] = node.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -129,8 +130,8 @@ func to_0_3_0(ea *ExitAction, ipMapper map[string]string) (shouldStop bool) {
|
|||
|
||||
// upgrade logs
|
||||
cronsun.GetDb().WithC(cronsun.Coll_JobLog, func(c *mgo.Collection) error {
|
||||
for ip, mid := range ipMapper {
|
||||
_, err = c.UpdateAll(bson.M{"node": ip}, bson.M{"$set": bson.M{"node": mid}})
|
||||
for ip, node := range nodesById {
|
||||
_, err = c.UpdateAll(bson.M{"node": ip}, bson.M{"$set": bson.M{"node": node.ID, "hostname": node.Hostname}})
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
fmt.Println("failed to upgrade job logs: ", err.Error())
|
||||
|
@ -142,5 +143,20 @@ func to_0_3_0(ea *ExitAction, ipMapper map[string]string) (shouldStop bool) {
|
|||
return err
|
||||
})
|
||||
|
||||
// upgrade logs
|
||||
cronsun.GetDb().WithC(cronsun.Coll_JobLatestLog, func(c *mgo.Collection) error {
|
||||
for ip, node := range nodesById {
|
||||
_, err = c.UpdateAll(bson.M{"node": ip}, bson.M{"$set": bson.M{"node": node.ID, "hostname": node.Hostname}})
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
fmt.Println("failed to upgrade job latest logs: ", err.Error())
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
shouldStop = true
|
||||
return err
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ func main() {
|
|||
log.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
web.EnsureJobLogIndex()
|
||||
|
||||
l, err := net.Listen("tcp", conf.Config.Web.BindAddr)
|
||||
if err != nil {
|
||||
|
|
|
@ -79,3 +79,9 @@ func (self *Mdb) FindOne(collection string, query interface{}, result interface{
|
|||
return c.Find(query).One(result)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *Mdb) RemoveId(collection string, id interface{}) error {
|
||||
return self.WithC(collection, func(c *mgo.Collection) error {
|
||||
return c.RemoveId(id)
|
||||
})
|
||||
}
|
||||
|
|
38
job.go
38
job.go
|
@ -67,7 +67,8 @@ type Job struct {
|
|||
LogExpiration int `json:"log_expiration"`
|
||||
|
||||
// 执行任务的结点,用于记录 job log
|
||||
runOn string
|
||||
runOn string
|
||||
hostname string
|
||||
// 用于存储分隔后的任务
|
||||
cmd []string
|
||||
// 控制同时执行任务数
|
||||
|
@ -185,9 +186,9 @@ func (j *Job) unlimit() {
|
|||
atomic.AddInt64(j.Count, -1)
|
||||
}
|
||||
|
||||
func (j *Job) Init(n string) {
|
||||
func (j *Job) Init(nodeID, hostname string) {
|
||||
var c int64
|
||||
j.Count, j.runOn = &c, n
|
||||
j.Count, j.runOn, j.hostname = &c, nodeID, hostname
|
||||
}
|
||||
|
||||
func (c *Cmd) lockTtl() int64 {
|
||||
|
@ -270,14 +271,14 @@ func (c *Cmd) lock() *locker {
|
|||
}
|
||||
|
||||
// 优先取结点里的值,更新 group 时可用 gid 判断是否对 job 进行处理
|
||||
func (j *JobRule) included(nid string, gs map[string]*Group) bool {
|
||||
for i, count := 0, len(j.NodeIDs); i < count; i++ {
|
||||
if nid == j.NodeIDs[i] {
|
||||
func (rule *JobRule) included(nid string, gs map[string]*Group) bool {
|
||||
for i, count := 0, len(rule.NodeIDs); i < count; i++ {
|
||||
if nid == rule.NodeIDs[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, gid := range j.GroupIDs {
|
||||
for _, gid := range rule.GroupIDs {
|
||||
if g, ok := gs[gid]; ok && g.Included(nid) {
|
||||
return true
|
||||
}
|
||||
|
@ -287,22 +288,22 @@ func (j *JobRule) included(nid string, gs map[string]*Group) bool {
|
|||
}
|
||||
|
||||
// 验证 timer 字段
|
||||
func (j *JobRule) Valid() error {
|
||||
func (rule *JobRule) Valid() error {
|
||||
// 注意 interface nil 的比较
|
||||
if j.Schedule != nil {
|
||||
if rule.Schedule != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(j.Timer) == 0 {
|
||||
if len(rule.Timer) == 0 {
|
||||
return ErrNilRule
|
||||
}
|
||||
|
||||
sch, err := cron.Parse(j.Timer)
|
||||
sch, err := cron.Parse(rule.Timer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid JobRule[%s], parse err: %s", j.Timer, err.Error())
|
||||
return fmt.Errorf("invalid JobRule[%s], parse err: %s", rule.Timer, err.Error())
|
||||
}
|
||||
|
||||
j.Schedule = sch
|
||||
rule.Schedule = sch
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -605,10 +606,16 @@ func (j *Job) Cmds(nid string, gs map[string]*Group) (cmds map[string]*Cmd) {
|
|||
return
|
||||
}
|
||||
|
||||
LOOP_TIMER_CMD:
|
||||
for _, r := range j.Rules {
|
||||
for _, id := range r.ExcludeNodeIDs {
|
||||
if nid == id {
|
||||
continue
|
||||
// 在当前定时器规则中,任务不会在该节点执行(节点被排除)
|
||||
// 但是任务可以在其它定时器中,在该节点被执行
|
||||
// 比如,一个定时器设置在凌晨 1 点执行,但是此时不想在这个节点执行,然后,
|
||||
// 同时又设置一个定时器在凌晨 2 点执行,这次这个任务由于某些原因,必须在当前节点执行
|
||||
// 下面的 LOOP_TIMER 标签,原因同上
|
||||
continue LOOP_TIMER_CMD
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -625,10 +632,11 @@ func (j *Job) Cmds(nid string, gs map[string]*Group) (cmds map[string]*Cmd) {
|
|||
}
|
||||
|
||||
func (j Job) IsRunOn(nid string, gs map[string]*Group) bool {
|
||||
LOOP_TIMER:
|
||||
for _, r := range j.Rules {
|
||||
for _, id := range r.ExcludeNodeIDs {
|
||||
if nid == id {
|
||||
continue
|
||||
continue LOOP_TIMER
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,8 @@ type JobLog struct {
|
|||
JobGroup string `bson:"jobGroup" json:"jobGroup"` // 任务分组,配合 Id 跳转用
|
||||
User string `bson:"user" json:"user"` // 执行此次任务的用户
|
||||
Name string `bson:"name" json:"name"` // 任务名称
|
||||
Node string `bson:"node" json:"node"` // 运行此次任务的节点 ip,索引
|
||||
Node string `bson:"node" json:"node"` // 运行此次任务的节点 id,索引
|
||||
Hostname string `bson:"hostname" json:"hostname"` // 运行此次任务的节点主机名称,索引
|
||||
Command string `bson:"command" json:"command,omitempty"` // 执行的命令,包括参数
|
||||
Output string `bson:"output" json:"output,omitempty"` // 任务输出的所有内容
|
||||
Success bool `bson:"success" json:"success"` // 是否执行成功
|
||||
|
@ -95,7 +96,8 @@ func CreateJobLog(j *Job, t time.Time, rs string, success bool) {
|
|||
Name: j.Name,
|
||||
User: j.User,
|
||||
|
||||
Node: j.runOn,
|
||||
Node: j.runOn,
|
||||
Hostname: j.hostname,
|
||||
|
||||
Command: j.Command,
|
||||
Output: rs,
|
||||
|
@ -124,7 +126,7 @@ func CreateJobLog(j *Job, t time.Time, rs string, success bool) {
|
|||
JobLog: jl,
|
||||
}
|
||||
latestLog.Id = ""
|
||||
if err := mgoDB.Upsert(Coll_JobLatestLog, bson.M{"node": jl.Node, "jobId": jl.JobId, "jobGroup": jl.JobGroup}, latestLog); err != nil {
|
||||
if err := mgoDB.Upsert(Coll_JobLatestLog, bson.M{"node": jl.Node, "hostname": jl.Hostname, "jobId": jl.JobId, "jobGroup": jl.JobGroup}, latestLog); err != nil {
|
||||
log.Errorf(err.Error())
|
||||
}
|
||||
|
||||
|
|
9
node.go
9
node.go
|
@ -23,8 +23,10 @@ const (
|
|||
// 执行 cron cmd 的进程
|
||||
// 注册到 /cronsun/node/<id>
|
||||
type Node struct {
|
||||
ID string `bson:"_id" json:"id"` // ip
|
||||
PID string `bson:"pid" json:"pid"` // 进程 pid
|
||||
ID string `bson:"_id" json:"id"` // machine id
|
||||
PID string `bson:"pid" json:"pid"` // 进程 pid
|
||||
IP string `bson:"ip" json:"ip"` // node ip
|
||||
Hostname string `bson:"hostname" json:"hostname"`
|
||||
|
||||
Version string `bson:"version" json:"version"`
|
||||
UpTime time.Time `bson:"up" json:"up"` // 启动时间
|
||||
|
@ -134,6 +136,9 @@ func WatchNode() client.WatchChan {
|
|||
|
||||
// On 结点实例启动后,在 mongoDB 中记录存活信息
|
||||
func (n *Node) On() {
|
||||
// remove old version(< 0.3.0) node info
|
||||
mgoDB.RemoveId(Coll_Node, n.IP)
|
||||
|
||||
n.Alived, n.Version, n.UpTime = true, Version, time.Now()
|
||||
if err := mgoDB.Upsert(Coll_Node, bson.M{"_id": n.ID}, n); err != nil {
|
||||
log.Errorf(err.Error())
|
||||
|
|
30
node/node.go
30
node/node.go
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
client "github.com/coreos/etcd/clientv3"
|
||||
"github.com/denisbrodbeck/machineid"
|
||||
|
||||
"github.com/shunfei/cronsun"
|
||||
"github.com/shunfei/cronsun/conf"
|
||||
|
@ -35,16 +36,29 @@ type Node struct {
|
|||
}
|
||||
|
||||
func NewNode(cfg *conf.Conf) (n *Node, err error) {
|
||||
mid, err := machineid.ProtectedID("cronsun")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ip, err := utils.LocalIP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
hostname = mid
|
||||
err = nil
|
||||
}
|
||||
|
||||
n = &Node{
|
||||
Client: cronsun.DefalutClient,
|
||||
Node: &cronsun.Node{
|
||||
ID: ip.String(),
|
||||
PID: strconv.Itoa(os.Getpid()),
|
||||
ID: mid,
|
||||
PID: strconv.Itoa(os.Getpid()),
|
||||
IP: ip.String(),
|
||||
Hostname: hostname,
|
||||
},
|
||||
Cron: cron.New(),
|
||||
|
||||
|
@ -62,6 +76,9 @@ func NewNode(cfg *conf.Conf) (n *Node, err error) {
|
|||
|
||||
// 注册到 /cronsun/node/xx
|
||||
func (n *Node) Register() (err error) {
|
||||
// remove old version(< 0.3.0) node info
|
||||
cronsun.DefalutClient.Delete(conf.Config.Node + n.IP)
|
||||
|
||||
pid, err := n.Node.Exist()
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -133,7 +150,7 @@ func (n *Node) loadJobs() (err error) {
|
|||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
job.Init(n.ID)
|
||||
job.Init(n.ID, n.Hostname)
|
||||
n.addJob(job, false)
|
||||
}
|
||||
|
||||
|
@ -142,6 +159,7 @@ func (n *Node) loadJobs() (err error) {
|
|||
|
||||
func (n *Node) addJob(job *cronsun.Job, notice bool) {
|
||||
n.link.addJob(job)
|
||||
|
||||
if job.IsRunOn(n.ID, n.groups) {
|
||||
n.jobs[job.ID] = job
|
||||
}
|
||||
|
@ -314,7 +332,7 @@ func (n *Node) groupAddNode(g *cronsun.Group) {
|
|||
n.link.delGroupJob(g.ID, jid)
|
||||
continue
|
||||
}
|
||||
job.Init(n.ID)
|
||||
job.Init(n.ID, n.Hostname)
|
||||
}
|
||||
|
||||
cmds := job.Cmds(n.ID, n.groups)
|
||||
|
@ -370,7 +388,7 @@ func (n *Node) watchJobs() {
|
|||
continue
|
||||
}
|
||||
|
||||
job.Init(n.ID)
|
||||
job.Init(n.ID, n.Hostname)
|
||||
n.addJob(job, true)
|
||||
case ev.IsModify():
|
||||
job, err := cronsun.GetJobFromKv(ev.Kv.Key, ev.Kv.Value)
|
||||
|
@ -379,7 +397,7 @@ func (n *Node) watchJobs() {
|
|||
continue
|
||||
}
|
||||
|
||||
job.Init(n.ID)
|
||||
job.Init(n.ID, n.Hostname)
|
||||
n.modJob(job)
|
||||
case ev.Type == client.EventTypeDelete:
|
||||
n.delJob(cronsun.GetIDFromKey(string(ev.Kv.Key)))
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"runtime"
|
||||
)
|
||||
|
||||
const Binary = "v0.2.3"
|
||||
const VersionNumber = "0.3.0"
|
||||
|
||||
var (
|
||||
Version = fmt.Sprintf("%s (build %s)", Binary, runtime.Version())
|
||||
Version = fmt.Sprintf("v%s (build %s)", VersionNumber, runtime.Version())
|
||||
)
|
||||
|
|
|
@ -13,6 +13,14 @@ import (
|
|||
"github.com/shunfei/cronsun"
|
||||
)
|
||||
|
||||
func EnsureJobLogIndex() {
|
||||
cronsun.GetDb().WithC(cronsun.Coll_JobLog, func(c *mgo.Collection) error {
|
||||
return c.EnsureIndex(mgo.Index{
|
||||
Key: []string{"beginTime"},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type JobLog struct{}
|
||||
|
||||
func (jl *JobLog) GetDetail(ctx *Context) {
|
||||
|
@ -43,7 +51,7 @@ func (jl *JobLog) GetDetail(ctx *Context) {
|
|||
}
|
||||
|
||||
func (jl *JobLog) GetList(ctx *Context) {
|
||||
nodes := getStringArrayFromQuery("nodes", ",", ctx.R)
|
||||
hostnames := getStringArrayFromQuery("hostnames", ",", ctx.R)
|
||||
names := getStringArrayFromQuery("names", ",", ctx.R)
|
||||
ids := getStringArrayFromQuery("ids", ",", ctx.R)
|
||||
begin := getTime(ctx.R.FormValue("begin"))
|
||||
|
@ -51,14 +59,11 @@ func (jl *JobLog) GetList(ctx *Context) {
|
|||
page := getPage(ctx.R.FormValue("page"))
|
||||
failedOnly := ctx.R.FormValue("failedOnly") == "true"
|
||||
pageSize := getPageSize(ctx.R.FormValue("pageSize"))
|
||||
sort := "-beginTime"
|
||||
if ctx.R.FormValue("sort") == "1" {
|
||||
sort = "beginTime"
|
||||
}
|
||||
orderBy := "-beginTime"
|
||||
|
||||
query := bson.M{}
|
||||
if len(nodes) > 0 {
|
||||
query["node"] = bson.M{"$in": nodes}
|
||||
if len(hostnames) > 0 {
|
||||
query["hostname"] = bson.M{"$in": hostnames}
|
||||
}
|
||||
|
||||
if len(ids) > 0 {
|
||||
|
@ -95,13 +100,13 @@ func (jl *JobLog) GetList(ctx *Context) {
|
|||
var err error
|
||||
if ctx.R.FormValue("latest") == "true" {
|
||||
var latestLogList []*cronsun.JobLatestLog
|
||||
latestLogList, pager.Total, err = cronsun.GetJobLatestLogList(query, page, pageSize, sort)
|
||||
latestLogList, pager.Total, err = cronsun.GetJobLatestLogList(query, page, pageSize, orderBy)
|
||||
for i := range latestLogList {
|
||||
latestLogList[i].JobLog.Id = bson.ObjectIdHex(latestLogList[i].RefLogId)
|
||||
pager.List = append(pager.List, &latestLogList[i].JobLog)
|
||||
}
|
||||
} else {
|
||||
pager.List, pager.Total, err = cronsun.GetJobLogList(query, page, pageSize, sort)
|
||||
pager.List, pager.Total, err = cronsun.GetJobLogList(query, page, pageSize, orderBy)
|
||||
}
|
||||
if err != nil {
|
||||
outJSONWithCode(ctx.W, http.StatusInternalServerError, err.Error())
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -40,31 +40,6 @@ export default {
|
|||
store,
|
||||
|
||||
mounted: function(){
|
||||
var vm = this;
|
||||
|
||||
this.$rest.GET('session?check=1').
|
||||
onsucceed(200, (resp) => {
|
||||
vm.$store.commit('enabledAuth', resp.enabledAuth);
|
||||
vm.$store.commit('setEmail', resp.email);
|
||||
vm.$store.commit('setRole', resp.role);
|
||||
|
||||
vm.$loadConfiguration();
|
||||
}).onfailed((data, xhr) => {
|
||||
if (xhr.status !== 401) {
|
||||
vm.$bus.$emit('error', data);
|
||||
} else {
|
||||
vm.$store.commit('enabledAuth', true);
|
||||
}
|
||||
vm.$router.push('/login');
|
||||
}).
|
||||
do();
|
||||
|
||||
this.$bus.$on('goLogin', () => {
|
||||
vm.$store.commit('setEmail', '');
|
||||
vm.$store.commit('setRole', 0);
|
||||
vm.$router.push('/login');
|
||||
});
|
||||
|
||||
$(this.$refs.langSelection).dropdown({
|
||||
onChange: function(value, text){
|
||||
var old = window.$.cookie('locale');
|
||||
|
|
|
@ -130,28 +130,26 @@ export default {
|
|||
chart.update();
|
||||
}
|
||||
|
||||
var renderNodeInfo = function(resp){
|
||||
vm.totalNodes = resp ? resp.length : 0;
|
||||
var online = 0;
|
||||
var offline = 0;
|
||||
var damaged = 0;
|
||||
for (var i in resp) {
|
||||
if (resp[i].alived && resp[i].connected) {
|
||||
online++;
|
||||
} else if (resp[i].alived && !resp[i].connected) {
|
||||
damaged++;
|
||||
} else if(!resp[i].alived) {
|
||||
offline++;
|
||||
}
|
||||
var nodes = this.$store.getters.nodes;
|
||||
this.totalNodes = nodes.length;
|
||||
var online = 0;
|
||||
var offline = 0;
|
||||
var damaged = 0;
|
||||
for (var id in nodes) {
|
||||
if (nodes[id].alived && nodes[id].connected) {
|
||||
online++;
|
||||
} else if (nodes[id].alived && !nodes[id].connected) {
|
||||
damaged++;
|
||||
} else if(!nodes[id].alived) {
|
||||
offline++;
|
||||
}
|
||||
|
||||
vm.totalOnlineNodes = online;
|
||||
vm.totalOfflineNodes = offline;
|
||||
vm.totalDamagedNodes = damaged;
|
||||
}
|
||||
|
||||
this.totalOnlineNodes = online;
|
||||
this.totalOfflineNodes = offline;
|
||||
this.totalDamagedNodes = damaged;
|
||||
|
||||
this.$rest.GET('/info/overview').onsucceed(200, renderJobInfo).do();
|
||||
this.$rest.GET('nodes').onsucceed(200, renderNodeInfo).do();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<i class="close icon"></i>
|
||||
<div class="header">{{$L('executing job: {job}', jobName)}}</div>
|
||||
<div class="content">
|
||||
<Dropdown :title="$L('node')" :items="nodes" v-on:change="changeNode"></Dropdown>
|
||||
<Dropdown :title="$L('node')" :items="nodes" v-on:change="changeNode" style="width:100%"></Dropdown>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div class="ui deny button">{{$L('cancel')}}</div>
|
||||
|
@ -51,8 +51,11 @@ export default {
|
|||
this.loading = true;
|
||||
this.$rest.GET('job/'+this.jobGroup+'-'+this.jobId+'/nodes').
|
||||
onsucceed(200, (resp)=>{
|
||||
resp.unshift('全部节点');
|
||||
vm.nodes = resp;
|
||||
var nodes = [{value: 'all nodes', name: vm.$L('all nodes')}];
|
||||
for (var i in resp) {
|
||||
nodes.push({value: resp[i], name: vm.$store.getters.getHostnameByID(resp[i])})
|
||||
}
|
||||
vm.nodes = nodes;
|
||||
}).
|
||||
onfailed((msg)=>{
|
||||
vm.$bus.$emit('error', msg);
|
||||
|
@ -65,7 +68,7 @@ export default {
|
|||
submit(){
|
||||
var vm = this;
|
||||
this.loading = true;
|
||||
var node = this.selectedNode === '全部节点' ? '' : this.selectedNode;
|
||||
var node = this.selectedNode === 'all nodes' ? '' : this.selectedNode;
|
||||
this.$rest.PUT('/job/'+this.jobGroup+'-'+this.jobId+'/execute?node='+node).
|
||||
onsucceed(204, ()=>{
|
||||
vm.$bus.$emit('success', '执行命令已发送,注意查看任务日志');
|
||||
|
@ -86,4 +89,4 @@ export default {
|
|||
Dropdown
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
</td>
|
||||
<td class="center aligned"><i class="icon" v-bind:class="{pause: job.pause, play: !job.pause, green: !job.pause}"></i></td>
|
||||
<td>{{job.group}}</td>
|
||||
<td>{{job.user}}</td>
|
||||
<td>{{job.user && job.user.length > 0 ? job.user : '-'}}</td>
|
||||
<td><router-link :to="'/job/edit/'+job.group+'/'+job.id">{{job.name}}</router-link></td>
|
||||
<td>
|
||||
<span v-if="!job.latestStatus">-</span>
|
||||
|
@ -110,12 +110,9 @@ export default {
|
|||
this.fetchList(this.buildQuery());
|
||||
}).do();
|
||||
|
||||
this.$rest.GET('nodes').onsucceed(200, (resp)=>{
|
||||
vm.nodes.push({name: vm.$L('all nodes'), value: ''});
|
||||
for (var i in resp) {
|
||||
vm.nodes.push(resp[i].id);
|
||||
}
|
||||
}).do();
|
||||
var nodes = Array.from(this.$store.getters.dropdownNodes);
|
||||
nodes.unshift({value: '', name: this.$L('all nodes')});
|
||||
vm.nodes = nodes;
|
||||
|
||||
$('.ui.checkbox').checkbox();
|
||||
},
|
||||
|
@ -186,7 +183,7 @@ export default {
|
|||
},
|
||||
|
||||
formatLatest: function(latest){
|
||||
return this.$L('on {node} took {times}, {begin ~ end}', latest.node, formatDuration(latest.beginTime, latest.endTime), formatTime(latest.beginTime, latest.endTime));
|
||||
return this.$L('on {node} took {times}, {begin ~ end}', latest.hostname, formatDuration(latest.beginTime, latest.endTime), formatTime(latest.beginTime, latest.endTime));
|
||||
},
|
||||
|
||||
showExecuteJobModal: function(jobName, jobGroup, jobId){
|
||||
|
|
|
@ -38,12 +38,7 @@ export default {
|
|||
|
||||
mounted: function(){
|
||||
var vm = this;
|
||||
this.$rest.GET('nodes').onsucceed(200, (resp)=>{
|
||||
for (var i in resp) {
|
||||
vm.activityNodes.push(resp[i].id);
|
||||
}
|
||||
}).do();
|
||||
|
||||
this.activityNodes = this.$store.getters.dropdownNodes;
|
||||
|
||||
this.$rest.GET('node/groups').onsucceed(200, (resp)=>{
|
||||
var groups = [];
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
</div>
|
||||
<div class="field">
|
||||
<label>{{$L('node')}}</label>
|
||||
<input type="text" v-model="nodes" :placeholder="$L('multiple IPs can separated by commas')">
|
||||
<input type="text" v-model="hostnames" :placeholder="$L('multiple Hostnames can separated by commas')">
|
||||
</div>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
|
@ -56,7 +56,7 @@
|
|||
<tbody>
|
||||
<tr v-for="log in list">
|
||||
<td><router-link class="item" :to="'/job/edit/'+log.jobGroup+'/'+log.jobId">{{log.name}}</router-link></td>
|
||||
<td>{{log.node}}</td>
|
||||
<td :title="log.node">{{$store.getters.getHostnameByID(log.node)}}</td>
|
||||
<td>{{log.user}}</td>
|
||||
<td :class="{warning: durationAttention(log.beginTime, log.endTime)}"><i class="attention icon" v-if="durationAttention(log.beginTime, log.endTime)"></i> {{formatTime(log)}}</td>
|
||||
<td :class="{error: !log.success}">
|
||||
|
@ -83,7 +83,7 @@ export default {
|
|||
loading: false,
|
||||
names: '',
|
||||
ids: '',
|
||||
nodes: '',
|
||||
hostnames: '',
|
||||
begin: '',
|
||||
end: '',
|
||||
latest: false,
|
||||
|
@ -114,7 +114,7 @@ export default {
|
|||
fillParams(){
|
||||
this.names = this.$route.query.names || '';
|
||||
this.ids = this.$route.query.ids || '';
|
||||
this.nodes = this.$route.query.nodes || '';
|
||||
this.hostnames = this.$route.query.hostnames || '';
|
||||
this.begin = this.$route.query.begin || '';
|
||||
this.end = this.$route.query.end || '';
|
||||
this.page = this.$route.query.page || 1;
|
||||
|
@ -139,7 +139,7 @@ export default {
|
|||
var params = [];
|
||||
if (this.names) params.push('names='+this.names);
|
||||
if (this.ids) params.push('ids='+this.ids);
|
||||
if (this.nodes) params.push('nodes='+this.nodes);
|
||||
if (this.hostnames) params.push('hostnames='+this.hostnames);
|
||||
if (this.begin) params.push('begin='+this.begin);
|
||||
if (this.end) params.push('end='+this.end);
|
||||
if (this.failedOnly) params.push('failedOnly=true');
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</div>
|
||||
<div class="ui segment">
|
||||
<p>
|
||||
<span class="title">{{$L('node')}}</span> {{log.node}}
|
||||
<span class="title">{{$L('node')}}</span> {{node.hostname}} [{{node.ip}}]
|
||||
</p>
|
||||
</div>
|
||||
<div class="ui segment">
|
||||
|
@ -65,6 +65,7 @@ export default {
|
|||
beginTime: new Date(),
|
||||
endTime: new Date()
|
||||
},
|
||||
node: {},
|
||||
error: ''
|
||||
}
|
||||
},
|
||||
|
@ -78,8 +79,17 @@ export default {
|
|||
mounted: function(){
|
||||
var vm = this;
|
||||
this.$rest.GET('log/'+this.$route.params.id).
|
||||
onsucceed(200, (resp)=>{vm.log = resp}).
|
||||
onfailed((data)=>{vm.error = data}).
|
||||
onsucceed(200, (resp)=>{
|
||||
vm.log = resp;
|
||||
vm.node = vm.$store.getters.getNodeByID(resp.node)
|
||||
}).
|
||||
onfailed((data, xhr) => {
|
||||
if (xhr.status === 404) {
|
||||
vm.error = vm.$L('log has been deleted')
|
||||
} else {
|
||||
vm.error = data
|
||||
}
|
||||
}).
|
||||
do();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<style scoped>
|
||||
.node {
|
||||
width: 140px;
|
||||
padding: 0 13px;
|
||||
border-radius: 3px;
|
||||
margin: 3px;
|
||||
display: inline-block;
|
||||
background: #e8e8e8;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
line-height: 1.9em;
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,6 @@
|
|||
<div>
|
||||
<div class="clearfix">
|
||||
<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" v-for="group in groups" v-bind:title="$L(group.title)">
|
||||
<i class="cube icon" v-bind:class="group.css"></i> {{group.nodes.length}} {{$L(group.name)}}
|
||||
</div>
|
||||
|
@ -42,7 +41,7 @@
|
|||
<div v-for="(node, nodeIndex) in group.nodes" class="node" v-bind:title="node.title">
|
||||
<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>
|
||||
{{node.id}}
|
||||
{{node.hostname || node.id+"(need to upgrade)"}}
|
||||
</router-link>
|
||||
<i v-if="groupIndex != 2" v-on:click="removeConfirm(groupIndex, nodeIndex, node.id)" class="icon remove"></i>
|
||||
</div>
|
||||
|
@ -70,30 +69,20 @@ export default {
|
|||
this.$rest.GET('version').onsucceed(200, (resp)=>{
|
||||
vm.version = resp;
|
||||
}).do();
|
||||
this.$rest.GET('nodes').onsucceed(200, (resp)=>{
|
||||
resp.sort(function(a, b){
|
||||
var aid = a.id.split('.');
|
||||
var bid = b.id.split('.');
|
||||
var ai = 0, bi = 0;
|
||||
for (var i in aid) {
|
||||
ai += (+aid[i])*Math.pow(255,3-i);
|
||||
bi += (+bid[i])*Math.pow(255,3-i);
|
||||
}
|
||||
return ai - bi;
|
||||
});
|
||||
for (var i in resp) {
|
||||
var n = resp[i];
|
||||
n.title = n.version + "\nstarted at: " + n.up
|
||||
if (n.alived && n.connected) {
|
||||
vm.groups[2].nodes.push(n);
|
||||
} else if (n.alived && !n.connected) {
|
||||
vm.groups[0].nodes.push(n);
|
||||
} else {
|
||||
vm.groups[1].nodes.push(n);
|
||||
}
|
||||
|
||||
var nodes = this.$store.getters.nodes;
|
||||
for (var id in nodes) {
|
||||
var n = nodes[id];
|
||||
n.title = n.ip + "\n" + n.id.substr(0, 16) + "\n" + n.version + "\nstarted at: " + n.up
|
||||
if (n.alived && n.connected) {
|
||||
vm.groups[2].nodes.push(n);
|
||||
} else if (n.alived && !n.connected) {
|
||||
vm.groups[0].nodes.push(n);
|
||||
} else {
|
||||
vm.groups[1].nodes.push(n);
|
||||
}
|
||||
vm.count = resp.length || 0;
|
||||
}).do();
|
||||
}
|
||||
vm.count = nodes.length || 0;
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -101,7 +90,7 @@ export default {
|
|||
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)=>{
|
||||
this.$rest.DELETE('node/'+nodeId).onsucceed(204, (resp) => {
|
||||
vm.groups[groupIndex].nodes.splice(nodeIndex, 1);
|
||||
}).do();
|
||||
}
|
||||
|
|
|
@ -30,7 +30,13 @@
|
|||
<router-link class="header" :to="'/node/group/'+g.id">{{g.name}}</router-link>
|
||||
<div class="description">
|
||||
<div class="ui middle large aligned divided list">
|
||||
<div class="item" v-for="n in g.nids">{{n}}</div>
|
||||
<div class="item" v-for="nodeID in g.nids">
|
||||
<span v-if="nodes[nodeID]">{{nodes[nodeID].hostname || nodes[nodeID].id}}
|
||||
<i class="arrow circle up icon red" v-if="nodes[nodeID].hostname == ''"></i>
|
||||
<i v-if="nodes[nodeID].hostname == ''">(need to upgrade)</i>
|
||||
</span>
|
||||
<span v-else :title="$L('node not found, was it removed?')">{{nodeID}} <i class="question circle icon red"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -63,6 +69,12 @@ export default {
|
|||
onend(()=>{vm.loading = false}).
|
||||
do();
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
nodes: function () {
|
||||
return this.$store.getters.nodes;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
<script>
|
||||
import Dropdown from './basic/Dropdown.vue';
|
||||
import {nodeDropdownData} from '../libraries/functions';
|
||||
|
||||
export default {
|
||||
name: 'node_group_edit',
|
||||
|
@ -57,11 +58,7 @@ export default {
|
|||
}
|
||||
|
||||
this.$rest.GET('nodes').onsucceed(200, (resp)=>{
|
||||
var allNodes = [];
|
||||
for (var i in resp) {
|
||||
allNodes.push(resp[i].id);
|
||||
}
|
||||
vm.allNodes = allNodes;
|
||||
vm.allNodes = nodeDropdownData(resp);
|
||||
}).do();
|
||||
},
|
||||
|
||||
|
@ -100,4 +97,4 @@ export default {
|
|||
Dropdown
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -30,7 +30,7 @@ var language = {
|
|||
'multiple names can separated by commas': 'Multiple names can separated by commas',
|
||||
'job ID': 'Job ID',
|
||||
'multiple IDs can separated by commas': 'Multiple IDs can separated by commas',
|
||||
'multiple IPs can separated by commas': 'Multiple IPs can separated by commas',
|
||||
'multiple Hostnames can separated by commas': 'Multiple Hostnames can separated by commas',
|
||||
'starting date': 'Starting date',
|
||||
'end date': 'End date',
|
||||
'failure only': 'Failure only',
|
||||
|
@ -75,6 +75,7 @@ var language = {
|
|||
'spend time': 'Spend time',
|
||||
'result': 'Result',
|
||||
'loading configurations': 'Loading configurations',
|
||||
'log has been deleted': 'Log has been deleted',
|
||||
|
||||
'job type': 'Job type',
|
||||
'common job': 'Common',
|
||||
|
@ -126,7 +127,8 @@ var language = {
|
|||
'select nodes': 'Select nodes',
|
||||
'select groups': 'Select groups',
|
||||
'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}?'
|
||||
'are you sure to remove the node {nodeId}?': 'Are you sure to remove the node {0}?',
|
||||
'node not found, was it removed?': 'Node not found, was it removed?'
|
||||
}
|
||||
|
||||
export default language;
|
||||
|
|
|
@ -30,7 +30,7 @@ var language = {
|
|||
'multiple names can separated by commas': '多个名称用英文逗号分隔',
|
||||
'job ID': '任务 ID',
|
||||
'multiple IDs can separated by commas': '多个 ID 用英文逗号分隔',
|
||||
'multiple IPs can separated by commas': '多个 IP 用英文逗号分隔',
|
||||
'multiple Hostnames can separated by commas': '多个主机名称用英文逗号分隔',
|
||||
'starting date': '起始日期',
|
||||
'end date': '截至日期',
|
||||
'failure only': '只看失败的任务',
|
||||
|
@ -77,6 +77,7 @@ var language = {
|
|||
'spend time': '耗时',
|
||||
'result': '结果',
|
||||
'loading configurations': '正在加载配置',
|
||||
'log has been deleted': '日志已经被删除',
|
||||
|
||||
'job type': '任务类型',
|
||||
'common job': '普通任务',
|
||||
|
@ -128,7 +129,8 @@ var language = {
|
|||
'select nodes': '选择节点',
|
||||
'select groups': '选择分组',
|
||||
'are you sure to delete the group {name}?': '确定删除分组 {0}?',
|
||||
'are you sure to remove the node {nodeId}?': '确定删除节点 {0}?'
|
||||
'are you sure to remove the node {nodeId}?': '确定删除节点 {0}?',
|
||||
'node not found, was it removed?': '不存在的节点,被删除了吗?'
|
||||
}
|
||||
|
||||
export default language;
|
||||
|
|
|
@ -53,4 +53,13 @@ var split = function(str, sep){
|
|||
return str.split(sep || ',');
|
||||
}
|
||||
|
||||
export {formatDuration, formatTime, formatNumber, split};
|
||||
var nodeDropdownData = function(nodeList){
|
||||
var data = [];
|
||||
nodeList.forEach(n => {
|
||||
data.push({value: n.id, name: n.hostname == '' ? n.id : n.hostname});
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export {formatDuration, formatTime, formatNumber, split, nodeDropdownData};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
window.$ = window.jQuery = require('jquery');
|
||||
require('semantic');
|
||||
require('semantic-ui/dist/semantic.min.css');
|
||||
import store from './vuex/store';
|
||||
|
||||
import Vue from 'vue';
|
||||
Vue.config.debug = true;
|
||||
|
@ -110,8 +111,55 @@ var router = new VueRouter({
|
|||
routes: routes
|
||||
});
|
||||
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
render: h => h(App),
|
||||
router: router
|
||||
bus.$on('goLogin', () => {
|
||||
store.commit('setEmail', '');
|
||||
store.commit('setRole', 0);
|
||||
router.push('/login');
|
||||
});
|
||||
|
||||
var initConf = new Promise((resolve) => {
|
||||
restApi.GET('session?check=1').
|
||||
onsucceed(200, (resp) => {
|
||||
store.commit('enabledAuth', resp.enabledAuth);
|
||||
store.commit('setEmail', resp.email);
|
||||
store.commit('setRole', resp.role);
|
||||
|
||||
restApi.GET('configurations').
|
||||
onsucceed(200, (resp) => {
|
||||
Vue.use((Vue) => Vue.prototype.$appConfig = resp);
|
||||
bus.$emit('conf_loaded', resp);
|
||||
|
||||
restApi.GET('nodes').onsucceed(200, (resp)=>{
|
||||
var nodes = {};
|
||||
for (var i in resp) {
|
||||
nodes[resp[i].id] = resp[i];
|
||||
}
|
||||
store.commit('setNodes', nodes);
|
||||
resolve();
|
||||
}).do();
|
||||
}).onfailed((data, xhr) => {
|
||||
bus.$emit('error', data ? data : xhr.status + ' ' + xhr.statusText);
|
||||
resolve();
|
||||
}).do();
|
||||
}).onfailed((data, xhr) => {
|
||||
if (xhr.status !== 401) {
|
||||
bus.$emit('error', data);
|
||||
} else {
|
||||
store.commit('enabledAuth', true);
|
||||
}
|
||||
router.push('/login');
|
||||
resolve()
|
||||
}).
|
||||
do();
|
||||
})
|
||||
|
||||
initConf.then(() => {
|
||||
new Vue({
|
||||
el: '#app',
|
||||
render: h => h(App),
|
||||
router: router
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -3,13 +3,15 @@ import Vuex from 'vuex';
|
|||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default new Vuex.Store({
|
||||
const store = new Vuex.Store({
|
||||
state: {
|
||||
enabledAuth: false,
|
||||
user: {
|
||||
email: '',
|
||||
role: 0
|
||||
}
|
||||
},
|
||||
nodes: {},
|
||||
dropdownNodes: []
|
||||
},
|
||||
|
||||
getters: {
|
||||
|
@ -23,6 +25,26 @@ export default new Vuex.Store({
|
|||
|
||||
enabledAuth: function (state) {
|
||||
return state.enabledAuth;
|
||||
},
|
||||
|
||||
nodes: function (state) {
|
||||
return state.nodes;
|
||||
},
|
||||
|
||||
getHostnameByID: function (state) {
|
||||
return (id) => {
|
||||
return state.nodes[id] ? state.nodes[id].hostname : id;
|
||||
}
|
||||
},
|
||||
|
||||
getNodeByID: function (state) {
|
||||
return (id) => {
|
||||
return state.nodes[id]
|
||||
}
|
||||
},
|
||||
|
||||
dropdownNodes: function (state) {
|
||||
return state.dropdownNodes;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -37,6 +59,17 @@ export default new Vuex.Store({
|
|||
|
||||
enabledAuth: function (state, enabledAuth) {
|
||||
state.enabledAuth = enabledAuth;
|
||||
},
|
||||
|
||||
setNodes: function (state, nodes) {
|
||||
state.nodes = nodes;
|
||||
var dn = []
|
||||
for (var i in nodes) {
|
||||
dn.push({value: nodes[i].id, name: nodes[i].hostname || nodes[i].id + '(need to upgrade)'})
|
||||
}
|
||||
state.dropdownNodes = dn;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default store
|
||||
|
|
Loading…
Reference in New Issue