修改任务列表也,日志详情页,添加分页 组件

pull/1/head
Doflatango 2017-02-14 18:42:41 +08:00 committed by miraclesu
parent 5991088eda
commit c1d4a12628
10 changed files with 221 additions and 24 deletions

View File

@ -10,7 +10,8 @@ import (
)
const (
Coll_JobLog = "job_log"
Coll_JobLog = "job_log"
Coll_JobLatestLog = "job_latest_log"
)
// 任务执行记录
@ -27,12 +28,17 @@ type JobLog struct {
EndTime time.Time `bson:"endTime" json:"endTime"` // 任务执行完毕时间,精确到毫秒
}
type JobLatestLog struct {
JobLog `bson:",inline"`
RefLogId string `bson:"refLogId,omitempty" json:"refLogId"`
}
func GetJobLogById(id bson.ObjectId) (l *JobLog, err error) {
err = mgoDB.FindId(Coll_JobLog, id, &l)
return
}
var projection = bson.M{"command": -1, "output": -1}
var selectForJobLogList = bson.M{"command": 0, "output": 0}
func GetJobLogList(query bson.M, page, size int, sort string) (list []*JobLog, total int, err error) {
err = mgoDB.WithC(Coll_JobLog, func(c *mgo.Collection) error {
@ -40,11 +46,25 @@ func GetJobLogList(query bson.M, page, size int, sort string) (list []*JobLog, t
if err != nil {
return err
}
return c.Find(query).Select(projection).Sort(sort).Skip((page - 1) * size).Limit(size).All(&list)
return c.Find(query).Select(selectForJobLogList).Sort(sort).Skip((page - 1) * size).Limit(size).All(&list)
})
return
}
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)
if err != nil {
return
}
m = make(map[string]*JobLatestLog, len(list))
for i := range list {
m[list[i].JobId] = list[i]
}
return
}
func CreateJobLog(j *Job, t time.Time, rs string, success bool) {
jl := JobLog{
Id: bson.NewObjectId(),
@ -65,4 +85,13 @@ func CreateJobLog(j *Job, t time.Time, rs string, success bool) {
if err := mgoDB.Insert(Coll_JobLog, jl); err != nil {
log.Error(err.Error())
}
latestLog := &JobLatestLog{
RefLogId: jl.Id.Hex(),
JobLog: jl,
}
latestLog.Id = ""
if err := mgoDB.Upsert(Coll_JobLatestLog, bson.M{"jobId": jl.JobId, "jobGroup": jl.JobGroup}, latestLog); err != nil {
log.Error(err.Error())
}
}

View File

@ -9,6 +9,7 @@ import (
"github.com/coreos/etcd/clientv3"
"github.com/gorilla/mux"
"sunteng/commons/log"
"sunteng/cronsun/conf"
"sunteng/cronsun/models"
)
@ -138,6 +139,11 @@ func (j *Job) GetGroups(w http.ResponseWriter, r *http.Request) {
}
func (j *Job) GetListByGroupName(w http.ResponseWriter, r *http.Request) {
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))
if err != nil {
@ -145,7 +151,8 @@ func (j *Job) GetListByGroupName(w http.ResponseWriter, r *http.Request) {
return
}
var jobList = make([]*models.Job, 0, resp.Count)
var jobIds []string
var jobList = make([]*jobStatus, 0, resp.Count)
for i := range resp.Kvs {
job := models.Job{}
err = json.Unmarshal(resp.Kvs[i].Value, &job)
@ -153,7 +160,17 @@ func (j *Job) GetListByGroupName(w http.ResponseWriter, r *http.Request) {
outJSONError(w, http.StatusInternalServerError, err.Error())
return
}
jobList = append(jobList, &job)
jobList = append(jobList, &jobStatus{Job: &job})
jobIds = append(jobIds, job.ID)
}
m, err := models.GetJobLatestLogListByJobIds(jobIds)
if err != nil {
log.Error("GetJobLatestLogListByJobIds error:", err.Error())
} else {
for i := range jobList {
jobList[i].LatestStatus = m[jobList[i].ID]
}
}
outJSON(w, jobList)

View File

@ -4,13 +4,13 @@ import (
"net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"sunteng/cronsun/models"
"time"
)
type JobLog struct{}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -17,6 +17,8 @@
<th class="collapsing center aligned">状态</th>
<th width="200px" class="center aligned">分组</th>
<th class="center aligned">名称</th>
<th class="center aligned">最近执行时间</th>
<th class="center aligned">执行结果</th>
</tr>
</thead>
<tbody>
@ -36,6 +38,14 @@
<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.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>
</td>
<td>
<span v-if="!job.latestStatus">-</span>
<router-link v-else :to="'/log/'+job.latestStatus.refLogId">{{job.latestStatus.success ? '成功' : '失败'}}</router-link>
</td>
</tr>
</tbody>
</table>
@ -45,6 +55,7 @@
<script>
import JobToolbar from './JobToolbar.vue';
import Dropdown from './basic/Dropdown.vue';
import Pager from './basic/Pager.vue';
export default {
name: 'job',
@ -93,12 +104,67 @@ export default {
this.$rest.POST('job/'+group+'-'+id, {"pause": isPause}).onsucceed(200, (resp)=>{
vm.refreshList();
}).do();
},
formatExecResult: function(st){
if (!st) return '-';
return
},
formatDuration: function(beginTime, endTime){
var d = new Date(endTime) - new Date(beginTime);
var s = '';
var day = d/86400000;
if (day >= 1) s += day.toString() + ' 天 ';
d = d%86400000;
var hour = d/3600000;
if (hour >= 1) s += hour.toString() + ' 小时 ';
d = d%3600000;
var min = d/60000;
if (min >= 1) s += min.toString() + ' 分钟 ';
d = d%60000;
var sec = d/1000;
if (sec >= 1) s += sec.toString() + ' 秒 ';
d = d%1000;
if (d >= 1) s = d.toString() + ' 毫秒';
return s;
},
formatTime: function(beginTime, endTime){
var now = new Date();
var bt = new Date(beginTime);
var et = new Date(endTime);
var s = this._formatTime(now, bt) + ' ~ ' + this._formatTime(now, et);
return s;
},
_formatTime: function(now, t){
var s = '';
if (now.getFullYear() != t.getFullYear()) {
s += t.getFullYear().toString() + '-';
}
s += this._formatNumber(t.getMonth()+1, 2).toString() + '-';
s += this._formatNumber(t.getDate(), 2) + ' ' + this._formatNumber(t.getHours(), 2) + ':' + this._formatNumber(t.getMinutes(), 2) + ':' + this._formatNumber(t.getSeconds(), 2);
return s;
},
// i > 0
_formatNumber: function(i, len){
var n = Math.ceil(Math.log10(i+1));
if (n >= len) return i.toString();
return '0'.repeat(len-n) + i.toString();
}
},
components: {
JobToolbar,
Dropdown
Dropdown,
Pager
}
}
</script>

View File

@ -3,7 +3,10 @@
<h4 class="ui horizontal divider header">定时器 - {{index}} <a href="#" v-on:click.prevent="remove">删除</a></h4>
<div class="two fields">
<div class="field">
<input type="text" v-bind:value="rule.timer" v-on:input="change('timer', $event.target.value)" placeholder="定时 * * * * *crontab 格式)"/>
<div class="ui icon input">
<input type="text" v-bind:value="rule.timer" v-on:input="change('timer', $event.target.value)" placeholder="定时 * 5 * * * *"/>
<i ref="ruletip" class="large help circle link icon" data-position="top right" data-content="<> <分钟> <小时> <> <月份> <星期>规则与 crontab 一样" data-variation="wide"></i>
</div>
</div>
<div class="field">
<Dropdown title="节点分组" v-bind:items="nodeGroups" multiple="true"></Dropdown>
@ -35,7 +38,11 @@ export default {
mounted: function(){
var vm = this;
this.$rest.GET('nodes').onsucceed(200, (resp)=>{vm.activityNodes = resp}).do();
this.$rest.GET('nodes').onsucceed(200, (resp)=>{
for (var i in resp) {
vm.activityNodes.push(resp[i].id);
}
}).do();
this.$rest.GET('nodes/groups').onsucceed(200, (resp)=>{
var groups = [];
for (var i in resp) {
@ -43,6 +50,8 @@ export default {
}
vm.nodeGroups = groups;
});
$(this.$refs.ruletip).popup();
},
methods: {

View File

@ -54,15 +54,17 @@ export default {
nodes: '',
begin: '',
end: '',
list: []
list: [],
total: 0,
currPage: 1
}
},
mounted: function(){
this.names = this.$route.query.names;
this.nodes = this.$route.query.nodes;
this.begin = this.$route.query.begin;
this.end = this.$route.query.end;
this.names = this.$route.query.names || '';
this.nodes = this.$route.query.nodes || '';
this.begin = this.$route.query.begin || '';
this.end = this.$route.query.end || '';
if (this.names || this.nodes || this.begin || this.end) this.submit();
},
@ -71,8 +73,12 @@ export default {
submit: function(){
this.loading = true;
var vm = this;
this.$rest.GET('logs?names='+this.name+'&nodes='+this.nodes+'&begin='+this.begin+'&end='+this.end
)
var params = [];
if (this.name) params.push('names='+this.name);
if (this.nodes) params.push('nodes='+this.nodes);
if (this.begin) params.push('begin='+this.begin);
if (this.end) params.push('end='+this.end);
this.$rest.GET('logs?'+params.join('&'))
.onsucceed(200, (resp)=>{vm.list = resp})
.onfailed((resp)=>{console.log(resp)})
.onend(()=>{vm.loading=false})
@ -122,13 +128,13 @@ export default {
s += t.getFullYear().toString() + '-';
}
s += this._formatNumber(t.getMonth()+1, 2).toString() + '-';
s += this._formatNumber(t.getDate(), 2) + ' ' + this._formatNumber(t.getHours(), 2) + ':' + this._formatNumber(t.getMinutes());
s += this._formatNumber(t.getDate(), 2) + ' ' + this._formatNumber(t.getHours(), 2) + ':' + this._formatNumber(t.getMinutes(), 2) + ':' + this._formatNumber(t.getSeconds(), 2);
return s;
},
// i > 0
_formatNumber: function(i, len){
var n = Math.ceil(Math.log10(i));
var n = Math.ceil(Math.log10(i+1));
if (n >= len) return i.toString();
return '0'.repeat(len-n) + i.toString();
}

View File

@ -13,6 +13,9 @@
<div class="ui segment">
<p>时间{{log.beginTime}} {{log.endTime}}</p>
</div>
<div class="ui segment">
<p>结果{{log.success ? '成功' : '失败'}}</p>
</div>
</div>
<h4 class="ui header">执行的命令</h4>
<pre class="ui grey inverted segment">{{log.command}}</pre>
@ -45,7 +48,7 @@ export default {
mounted: function(){
var vm = this;
this.$rest.GET('log/'+this.$route.params.id).
onsucceed(200, (resp)=>{/*vm.log = resp*/}).
onsucceed(200, (resp)=>{vm.log = resp}).
onfailed((data)=>{vm.error = data.error}).
do();
}

View File

@ -0,0 +1,66 @@
<template>
<div style="text-align: center;">
<div class="ui icon buttons">
<router-link :to="pageURL(startPage-1)" class="ui button" :class="{disabled: startPage<=1}"><i class="angle left icon"></i></router-link>
<router-link :to="pageURL(startPage + n - 1)" v-for="n in pageBtnNum" class="ui button" :class="{blue: startPage+n-1 == _current}">{{startPage + n-1}}</router-link>
<router-link :to="pageURL(startPage+length)" class="ui button" :class="{disabled: startPage+length>total}"><i class="angle right icon"></i></router-link>
</div>
</div>
</template>
<script>
export default {
name: 'pager',
props: ['total', 'length', 'pageVar'],
data: function(){
return {
_pagevar: '',
_current: 1,
}
},
created: function(){
this._pagevar = this.pageVar || 'page';
this._current = this.$route.query[this._pagevar] || 1;
},
mounted: function(){
console.log('mounted');
},
methods: {
pageURL: function(page){
return this.url + this._pagevar + '=' + page;
}
},
watch: {
'$route': function(){
this._current = this.$route.query[this._pagevar] || 1;
}
},
computed: {
pageBtnNum: function(){
console.log('pageBtnNum');
var remainingPage = this.total - this.startPage;
return remainingPage <= this.length ? this.total - this.startPage + 1 : this.length;
},
startPage: function(){
console.log('startPage');
return Math.floor((this._current-1)/this.length) * this.length+1;
},
url: function(){
console.log('url');
var query = [];
for (var k in this.$route.query) {
if (this._pagevar === k) continue;
query.push(k+'='+this.$route.query[k]);
}
return this.$route.path+'?'+query.join('&') + '&';
}
}
}
</script>