bug修复,日志支持查询在所有节点上最新的日志

pull/1/head
Doflatango 2017-02-16 11:57:58 +08:00 committed by miraclesu
parent 63f7b2ba52
commit 3b7139f52d
11 changed files with 125 additions and 47 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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")

28
web/ui/dist/build.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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
}

View File

@ -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' ? '添加' : '更新'}}任务&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<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">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ID# {{job.id}}</em>
</h3>
<div class="two fields">
<div class="field">

View File

@ -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>

View File

@ -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;
},

View File

@ -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();
}
}