mirror of https://github.com/shunfei/cronsun
bug修复,日志支持查询在所有节点上最新的日志
parent
63f7b2ba52
commit
3b7139f52d
|
@ -64,7 +64,10 @@ func GetJobLatestLogList(query bson.M, page, size int, sort string) (list []*Job
|
|||
|
||||
func GetJobLatestLogListByJobIds(jobIds []string) (m map[string]*JobLatestLog, err error) {
|
||||
var list []*JobLatestLog
|
||||
err = mgoDB.AllSelect(Coll_JobLatestLog, bson.M{"jobId": bson.M{"$in": jobIds}}, selectForJobLogList, &list)
|
||||
|
||||
err = mgoDB.WithC(Coll_JobLatestLog, func(c *mgo.Collection) error {
|
||||
return c.Find(bson.M{"jobId": bson.M{"$in": jobIds}}).Select(selectForJobLogList).Sort("beginTime").All(&list)
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
12
web/job.go
12
web/job.go
|
@ -135,17 +135,21 @@ func (j *Job) GetGroups(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
sort.Strings(groupList)
|
||||
outJSON(w, groupList)
|
||||
|
||||
}
|
||||
|
||||
func (j *Job) GetListByGroupName(w http.ResponseWriter, r *http.Request) {
|
||||
func (j *Job) GetList(w http.ResponseWriter, r *http.Request) {
|
||||
group := strings.TrimSpace(r.FormValue("group"))
|
||||
var prefix = conf.Config.Cmd
|
||||
if len(group) != 0 {
|
||||
prefix += group
|
||||
}
|
||||
|
||||
type jobStatus struct {
|
||||
*models.Job
|
||||
LatestStatus *models.JobLatestLog `json:"latestStatus"`
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
resp, err := models.DefalutClient.Get(conf.Config.Cmd+vars["name"], clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByKey, clientv3.SortAscend))
|
||||
resp, err := models.DefalutClient.Get(prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByKey, clientv3.SortAscend))
|
||||
if err != nil {
|
||||
outJSONError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"gopkg.in/mgo.v2"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
|
||||
"math"
|
||||
"sunteng/cronsun/models"
|
||||
)
|
||||
|
||||
|
@ -23,6 +24,11 @@ func (jl *JobLog) GetDetail(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if !bson.IsObjectIdHex(id) {
|
||||
outJSONWithCode(w, http.StatusBadRequest, "invalid ObjectId.")
|
||||
return
|
||||
}
|
||||
|
||||
logDetail, err := models.GetJobLogById(bson.ObjectIdHex(id))
|
||||
if err != nil {
|
||||
statusCode := http.StatusInternalServerError
|
||||
|
@ -40,6 +46,7 @@ func (jl *JobLog) GetDetail(w http.ResponseWriter, r *http.Request) {
|
|||
func (jl *JobLog) GetList(w http.ResponseWriter, r *http.Request) {
|
||||
nodes := GetStringArrayFromQuery("nodes", ",", r)
|
||||
names := GetStringArrayFromQuery("names", ",", r)
|
||||
ids := GetStringArrayFromQuery("ids", ",", r)
|
||||
begin := getTime(r.FormValue("begin"))
|
||||
end := getTime(r.FormValue("end"))
|
||||
page := getPage(r.FormValue("page"))
|
||||
|
@ -53,6 +60,11 @@ func (jl *JobLog) GetList(w http.ResponseWriter, r *http.Request) {
|
|||
if len(nodes) > 0 {
|
||||
query["node"] = bson.M{"$in": nodes}
|
||||
}
|
||||
|
||||
if len(ids) > 0 {
|
||||
query["jobId"] = bson.M{"$in": ids}
|
||||
}
|
||||
|
||||
if len(names) > 0 {
|
||||
var search []bson.M
|
||||
for _, k := range names {
|
||||
|
@ -77,13 +89,22 @@ func (jl *JobLog) GetList(w http.ResponseWriter, r *http.Request) {
|
|||
List []*models.JobLog `json:"list"`
|
||||
}
|
||||
var err error
|
||||
pager.List, pager.Total, err = models.GetJobLogList(query, page, pageSize, sort)
|
||||
if r.FormValue("latest") == "true" {
|
||||
var latestLogList []*models.JobLatestLog
|
||||
latestLogList, pager.Total, err = models.GetJobLatestLogList(query, page, pageSize, sort)
|
||||
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 = models.GetJobLogList(query, page, pageSize, sort)
|
||||
}
|
||||
if err != nil {
|
||||
outJSONError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
pager.Total /= pageSize
|
||||
pager.Total = int(math.Ceil(float64(pager.Total) / float64(pageSize)))
|
||||
outJSON(w, pager)
|
||||
}
|
||||
|
||||
|
|
|
@ -17,12 +17,12 @@ func InitRouters() (s *http.Server, err error) {
|
|||
r := mux.NewRouter()
|
||||
subrouter := r.PathPrefix("/v1").Subrouter()
|
||||
|
||||
// get job list
|
||||
h := BaseHandler{Handle: jobHandler.GetList}
|
||||
subrouter.Handle("/jobs", h).Methods("GET")
|
||||
// get a job group list
|
||||
h := BaseHandler{Handle: jobHandler.GetGroups}
|
||||
h = BaseHandler{Handle: jobHandler.GetGroups}
|
||||
subrouter.Handle("/job/groups", h).Methods("GET")
|
||||
// get a job group by group name
|
||||
h = BaseHandler{Handle: jobHandler.GetListByGroupName}
|
||||
subrouter.Handle("/job/group/{name}", h).Methods("GET")
|
||||
// create/update a job
|
||||
h = BaseHandler{Handle: jobHandler.UpdateJob}
|
||||
subrouter.Handle("/job", h).Methods("PUT")
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -3,14 +3,17 @@
|
|||
</style>
|
||||
<template>
|
||||
<div>
|
||||
<JobToolbar class="clearfix"/>
|
||||
<div class="clearfix">
|
||||
<router-link class="ui right floated primary button" to="/job/create"><i class="add to calendar icon"></i> 新任务</router-link>
|
||||
<button class="ui right floated icon button" v-on:click="refresh"><i class="refresh icon"></i></button>
|
||||
</div>
|
||||
<form class="ui form">
|
||||
<div class="field">
|
||||
<label>选择一个分组显示其下的任务</label>
|
||||
<Dropdown title="选择分组" v-bind:items="groups" v-on:change="changeGroup"/>
|
||||
<Dropdown title="选择分组" v-bind:items="groups" v-on:change="changeGroup" selected="group"/>
|
||||
</div>
|
||||
</form>
|
||||
<table class="ui hover celled striped blue table" v-if="jobs.length > 0">
|
||||
<table class="ui hover blue table" v-if="jobs.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="collapsing center aligned">操作</th>
|
||||
|
@ -40,11 +43,12 @@
|
|||
<td>{{job.name}}</td>
|
||||
<td>
|
||||
<span v-if="!job.latestStatus">-</span>
|
||||
<span v-else>{{formatTime(job.latestStatus.beginTime, job.latestStatus.endTime)}},耗时 {{formatDuration(job.latestStatus.beginTime, job.latestStatus.endTime)}}</span>
|
||||
<span v-else>{{formatTime(job.latestStatus.beginTime, job.latestStatus.endTime)}},于 {{job.latestStatus.node}} 耗时 {{formatDuration(job.latestStatus.beginTime, job.latestStatus.endTime)}}</span>
|
||||
</td>
|
||||
<td>
|
||||
<td :class="{error: job.latestStatus && !job.latestStatus.success}">
|
||||
<span v-if="!job.latestStatus">-</span>
|
||||
<router-link v-else :to="'/log/'+job.latestStatus.refLogId">{{job.latestStatus.success ? '成功' : '失败'}}</router-link>
|
||||
<router-link v-else :to="'/log/'+job.latestStatus.refLogId">{{job.latestStatus.success ? '成功' : '失败'}}</router-link> |
|
||||
<router-link :to="{path: 'log', query: {latest:true, ids: job.id}}">latest</router-link>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -53,7 +57,6 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import JobToolbar from './JobToolbar.vue';
|
||||
import Dropdown from './basic/Dropdown.vue';
|
||||
import Pager from './basic/Pager.vue';
|
||||
|
||||
|
@ -69,22 +72,39 @@ export default {
|
|||
|
||||
mounted: function(){
|
||||
var vm = this;
|
||||
this.group = this.$route.query.group || '';
|
||||
|
||||
this.$rest.GET('job/groups').onsucceed(200, (resp)=>{
|
||||
!resp.includes('default') && resp.unshift('default');
|
||||
resp.unshift({value: '', name: '所有任务'});
|
||||
vm.groups = resp;
|
||||
this.fetchList(this.buildQuery());
|
||||
}).do();
|
||||
},
|
||||
|
||||
watch: {
|
||||
'$route': function(){
|
||||
this.group = this.$route.query.group || '';
|
||||
this.fetchList(this.buildQuery());
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
changeGroup: function(val, text){
|
||||
var vm = this;
|
||||
this.group = val;
|
||||
this.refreshList();
|
||||
this.$router.push('job?'+this.buildQuery());
|
||||
},
|
||||
|
||||
refreshList: function(){
|
||||
buildQuery: function(){
|
||||
var params = [];
|
||||
if (this.group) params.push('group='+this.group);
|
||||
return params.join('&');
|
||||
},
|
||||
|
||||
fetchList: function(query){
|
||||
var vm = this;
|
||||
this.$rest.GET('job/group/'+this.group).onsucceed(200, (resp)=>{
|
||||
this.$rest.GET('jobs?'+query).onsucceed(200, (resp)=>{
|
||||
vm.jobs = resp;
|
||||
vm.$nextTick(()=>{
|
||||
$(vm.$el).find('table .ui.dropdown').dropdown();
|
||||
|
@ -92,6 +112,10 @@ export default {
|
|||
}).do();
|
||||
},
|
||||
|
||||
refresh: function(){
|
||||
this.fetchList(this.buildQuery());
|
||||
},
|
||||
|
||||
removeJob: function(group, id, index){
|
||||
var vm = this;
|
||||
this.$rest.DELETE('job/'+group+'-'+id).onsucceed(204, (resp)=>{
|
||||
|
@ -132,6 +156,7 @@ export default {
|
|||
d = d%1000;
|
||||
if (d >= 1) s = d.toString() + ' 毫秒';
|
||||
|
||||
if (s.length == 0) s = "0 毫秒";
|
||||
return s;
|
||||
},
|
||||
|
||||
|
@ -162,7 +187,6 @@ export default {
|
|||
},
|
||||
|
||||
components: {
|
||||
JobToolbar,
|
||||
Dropdown,
|
||||
Pager
|
||||
}
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
</div>
|
||||
<form v-else class="ui form" v-bind:class="{loading:loading}" v-on:submit.prevent>
|
||||
<h3 class="ui header">{{action == 'CREATE' ? '添加' : '更新'}}任务
|
||||
<div class="ui toggle checkbox">
|
||||
<input type="checkbox" class="hidden" v-bind:checked="!job.pause">
|
||||
<label v-bind:style="{color: (job.pause?'red':'green')+' !important'}">{{job.pause ? '任务已暂停' : '开启'}}</label>
|
||||
</div>
|
||||
<div class="ui toggle checkbox">
|
||||
<input type="checkbox" class="hidden" v-bind:checked="!job.pause">
|
||||
<label v-bind:style="{color: (job.pause?'red':'green')+' !important'}">{{job.pause ? '任务已暂停' : '开启'}}</label>
|
||||
</div>
|
||||
<em v-if="job.id"> ID# {{job.id}}</em>
|
||||
</h3>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<router-link class="ui right floated primary button" to="/job/create"><i class="add to calendar icon"></i> 新任务</router-link>
|
||||
<button class="ui right floated icon button"><i class="refresh icon"></i></button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
<template>
|
||||
<div>
|
||||
<form class="ui form" method="GET" v-bind:class="{loading:loading}" v-on:submit.prevent>
|
||||
<div class="field">
|
||||
<label>任务名称</label>
|
||||
<input type="text" ref="name" v-model="names" placeholder="多个名称用英文逗号分隔">
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>任务名称</label>
|
||||
<input type="text" v-model="names" placeholder="多个名称用英文逗号分隔">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>任务 ID</label>
|
||||
<input type="text" v-model="ids" placeholder="多个 ID 用英文逗号分隔">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>运行节点</label>
|
||||
<input type="text" ref="name" v-model="nodes" placeholder="ip,多个 ip 用英文逗号分隔">
|
||||
<input type="text" v-model="nodes" placeholder="ip,多个 ip 用英文逗号分隔">
|
||||
</div>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
|
@ -19,6 +25,12 @@
|
|||
<input type="date" v-model="end">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div ref="latest" class="ui checkbox">
|
||||
<input type="checkbox" tabindex="0" class="hidden" v-model="latest">
|
||||
<label>只看每个任务在每个节点上最后一次运行的结果</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<button class="fluid ui button" type="button" v-on:click="submit">查询</button>
|
||||
</div>
|
||||
|
@ -56,9 +68,11 @@ export default {
|
|||
return {
|
||||
loading: false,
|
||||
names: '',
|
||||
ids: '',
|
||||
nodes: '',
|
||||
begin: '',
|
||||
end: '',
|
||||
latest: false,
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1
|
||||
|
@ -67,21 +81,28 @@ export default {
|
|||
|
||||
mounted: function(to, from, next){
|
||||
this.names = this.$route.query.names || '';
|
||||
this.ids = this.$route.query.ids || '';
|
||||
this.nodes = this.$route.query.nodes || '';
|
||||
this.begin = this.$route.query.begin || '';
|
||||
this.end = this.$route.query.end || '';
|
||||
this.page = this.$route.query.page || 1;
|
||||
this.latest = this.$route.query.latest ? true : false;
|
||||
this.fetchList(this.buildQuery());
|
||||
|
||||
var vm = this;
|
||||
$(this.$refs.latest).checkbox({'onChange': ()=>{vm.latest = !vm.latest}});
|
||||
},
|
||||
|
||||
watch: {
|
||||
'$route': function(){
|
||||
this.names = this.$route.query.names || '';
|
||||
this.ids = this.$route.query.ids || '';
|
||||
this.nodes = this.$route.query.nodes || '';
|
||||
this.begin = this.$route.query.begin || '';
|
||||
this.end = this.$route.query.end || '';
|
||||
this.page = this.$route.query.page || 1;
|
||||
|
||||
this.latest = this.$route.query.latest == 'true' ? true : false;
|
||||
|
||||
this.fetchList(this.buildQuery());
|
||||
}
|
||||
},
|
||||
|
@ -103,11 +124,13 @@ export default {
|
|||
buildQuery(){
|
||||
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.begin) params.push('begin='+this.begin);
|
||||
if (this.end) params.push('end='+this.end);
|
||||
if (this.page == 0) this.page = 1;
|
||||
params.push('page='+this.page);
|
||||
if (this.latest) params.push('latest=true');
|
||||
return params.join('&');
|
||||
},
|
||||
|
||||
|
@ -141,6 +164,7 @@ export default {
|
|||
d = d%1000;
|
||||
if (d >= 1) s = d.toString() + ' 毫秒';
|
||||
|
||||
if (s.length == 0) s = "0 毫秒";
|
||||
return s;
|
||||
},
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ export default {
|
|||
var vm = this;
|
||||
this.$rest.GET('log/'+this.$route.params.id).
|
||||
onsucceed(200, (resp)=>{vm.log = resp}).
|
||||
onfailed((data)=>{vm.error = data.error}).
|
||||
onfailed((data)=>{vm.error = data}).
|
||||
do();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue