日志显示列表和日志详细信息界面

pull/1/head
Doflatango 2017-02-07 18:05:59 +08:00 committed by miraclesu
parent 5b4900d6c4
commit fefb89c9cb
8 changed files with 296 additions and 26 deletions

29
models/job_log.go Normal file
View File

@ -0,0 +1,29 @@
package models
import (
"time"
"gopkg.in/mgo.v2/bson"
)
// 任务执行记录
type JobLog struct {
Id bson.ObjectId `bson:"_id,omitempty" json:"id"`
JobId string `bson:"jobId" json:"jobId"` // 任务 Id索引
JobGroup string `bson:"jobGroup" json:"jobGroup"` // 任务分组,配合 Id 跳转用
Name string `bson:"name" json:"name"` // 任务名称
Node string `bson:"node" json:"node"` // 运行此次任务的节点 ip索引
Command string `bson:"command" json:"command,omitempty"` // 执行的命令,包括参数
Output string `bson:"output" json:"output,omitempty"` // 任务输出的所有内容
ExitCode uint8 `bson:"exitCode" json:"exitCode"` // 脚本退出状态码
BeginTime time.Time `bson:"beginTime" json:"beginTime"` // 任务开始执行时间,精确到毫秒,索引
EndTime time.Time `bson:"endTime" json:"endTime"` // 任务执行完毕时间,精确到毫秒
}
func GetJobLogById(id bson.ObjectId) (*JobLog, error) {
return nil, nil
}
func GetJobLogList(query interface{}, skip, limit int) (list []*JobLog, err error) {
return
}

70
web/job_log.go Normal file
View File

@ -0,0 +1,70 @@
package web
import (
"net/http"
"strings"
"time"
"github.com/gorilla/mux"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"sunteng/cronsun/models"
)
type JobLog struct{}
func (jl *JobLog) GetDetail(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := strings.TrimSpace(vars["id"])
if len(id) == 0 {
outJSONWithCode(w, http.StatusBadRequest, "empty log id.")
return
}
logDetail, err := models.GetJobLogById(bson.ObjectIdHex(id))
if err != nil {
statusCode := http.StatusInternalServerError
if err == mgo.ErrNotFound {
statusCode = http.StatusNotFound
err = nil
}
outJSONWithCode(w, statusCode, err)
return
}
outJSON(w, logDetail)
}
func (jl *JobLog) GetList(w http.ResponseWriter, r *http.Request) {
list := []*models.JobLog{
&models.JobLog{
Id: bson.NewObjectId(),
Name: "test1",
JobId: "test1",
Node: "192.168.1.2",
ExitCode: 0,
BeginTime: time.Now(),
EndTime: time.Now().Add(time.Duration(time.Minute)),
},
&models.JobLog{
Id: bson.NewObjectId(),
Name: "test2",
JobId: "test2",
Node: "192.168.1.2",
ExitCode: 1,
BeginTime: time.Now(),
EndTime: time.Now().Add(time.Duration(350 * time.Millisecond)),
},
&models.JobLog{
Id: bson.NewObjectId(),
Name: "test3",
JobId: "test3",
Node: "192.168.1.3",
ExitCode: 0,
BeginTime: time.Now(),
EndTime: time.Now().Add(time.Duration(time.Hour * 12)),
},
}
outJSON(w, list)
}

View File

@ -12,6 +12,7 @@ import (
func InitRouters() (s *http.Server, err error) {
jobHandler := &Job{}
nodeHandler := &Node{}
jobLogHandler := &JobLog{}
r := mux.NewRouter()
subrouter := r.PathPrefix("/v1").Subrouter()
@ -35,6 +36,13 @@ func InitRouters() (s *http.Server, err error) {
h = BaseHandler{Handle: jobHandler.DeleteJob}
subrouter.Handle("/job/{group}-{id}", h).Methods("DELETE")
// get job log list
h = BaseHandler{Handle: jobLogHandler.GetList}
subrouter.Handle("/logs", h).Methods("GET")
// get job log
h = BaseHandler{Handle: jobLogHandler.GetDetail}
subrouter.Handle("/log/{id}", h).Methods("GET")
h = BaseHandler{Handle: nodeHandler.GetActivityNodeList}
subrouter.Handle("/node/activitys", h).Methods("GET")
// get node group list

View File

@ -1,5 +1,8 @@
<template>
<form class="ui form segment" v-bind:class="{loading:loading}" v-on:submit.prevent>
<div v-if="error != ''" class="ui negative message">
<div class="header"><i class="attention icon"></i> {{error}}</div>
</div>
<form v-else class="ui form segment" 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">
@ -11,7 +14,7 @@
<label>任务名称</label>
<input type="text" ref="name" v-bind:value="job.name" v-on:input="updateValue($event.target.value)" placeholder="任务名称">
</div>
<div class="field">
<div class="field">
<label>任务分组</label>
<Dropdown title="选择分组" allowAdditions=true v-bind:items="groups" v-bind:selected="job.group" v-on:change="changeGroup"/>
</div>
@ -28,7 +31,7 @@
<div class="field">
<button class="fluid ui button" v-on:click="addNewTimer" type="button"><i class="history icon"></i> 添加定时器</button>
</div>
<div class="field">
<div class="field">
<button class="fluid blue ui button" type="button" v-on:click="submit"><i class="upload icon"></i> 保存任务</button>
</div>
</div>
@ -53,7 +56,8 @@ export default {
cmd: '',
pause: false,
rules: []
}
},
error: ''
}
},
@ -99,7 +103,10 @@ export default {
this.action = 'CREATE';
} else {
this.action = 'UPDATE';
this.$rest.GET('job/'+this.$route.params.group+'-'+this.$route.params.id).onsucceed(200, (resp)=>{vm.job = resp}).do();
this.$rest.GET('job/'+this.$route.params.group+'-'+this.$route.params.id).
onsucceed(200, (resp)=>{vm.job = resp}).
onfailed((data)=>{vm.error = data.error}).
do();
}
this.$rest.GET('job/groups').onsucceed(200, (resp)=>{

View File

@ -10,11 +10,11 @@
</div>
</div>
<div class="field">
<label><strong style="color:green;">+</strong> 同时在这些节点上面运行任务</label>
<label>同时在这些节点上面运行任务</label>
<Dropdown title="选择节点" v-bind:items="activityNodes" multiple="true"></Dropdown>
</div>
<div class="field">
<label><strong style="color:red;">-</strong> 不在这些节点上面运行任务</label>
<label>不在这些节点上面运行任务</label>
<Dropdown title="选择节点" v-bind:items="activityNodes" multiple="true"></Dropdown>
</div>
</div>

View File

@ -1,26 +1,127 @@
<template>
<table class="ui purple table">
<thead>
<tr><th>Food</th>
<th>Calories</th>
<th>Protein</th>
</tr></thead><tbody>
<tr>
<td>Apples</td>
<td>200</td>
<td>0g</td>
</tr>
<tr>
<td>Orange</td>
<td>310</td>
<td>0g</td>
</tr>
</tbody>
</table>
<div>
<form class="ui form segment fixed" v-bind:class="{loading:loading}" v-on:submit.prevent>
<div class="field">
<label>任务名称</label>
<input type="text" ref="name" v-bind:value="names" v-on:input="updateValue($event.target.value)" placeholder="多个名称用英文逗号分隔">
</div>
<div class="field">
<label>运行节点</label>
<input type="text" ref="name" v-bind:value="nodes" v-on:input="updateValue($event.target.value)" placeholder="ip多个 ip 用英文逗号分隔">
</div>
<div class="two fields">
<div class="field">
<label>开始时间</label>
<input type="date"/>
</div>
<div class="field">
<label>结束时间</label>
<input type="date"/>
</div>
</div>
<div class="field">
<button class="fluid ui button" type="button" v-on:click="submit">查询</button>
</div>
</form>
<table class="ui selectable green table">
<thead>
<tr>
<th class="center aligned">任务名称</th>
<th class="center aligned">运行节点</th>
<th class="center aligned">执行时间</th>
<th class="center aligned">状态码</th>
</tr>
</thead>
<tbody>
<tr v-for="log in list">
<td><router-link class="item" :to="/log/+log.id">{{log.name}}</router-link></td>
<td>{{log.node}}</td>
<td :class="{warning: durationAttention(log.beginTime, log.endTime)}"><i class="attention icon" v-if="durationAttention(log.beginTime, log.endTime)"></i> {{formatTime(log.beginTime, log.endTime)}}{{formatDuration(log.beginTime, log.endTime)}}</td>
<td :class="{error: log.exitCode != 0}">{{log.exitCode}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'log'
name: 'log',
data: function(){
return {
loading: false,
names: '',
nodes: '',
list: []
}
},
methods: {
updateValue: function(){},
submit: function(){
this.loading = true;
var vm = this;
this.$rest.GET('logs')
.onsucceed(200, (resp)=>{vm.list = resp})
.onfailed((resp)=>{console.log(resp)})
.onend(()=>{vm.loading=false})
.do();
},
durationAttention: function(beginTime, endTime){
var d = new Date(endTime) - new Date(beginTime);
return d > 3600000*6;
},
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());
return s;
},
// i > 0
_formatNumber: function(i, len){
var n = Math.ceil(Math.log10(i));
if (n >= len) return i.toString();
return '0'.repeat(len-n) + i.toString();
}
}
}
</script>

View File

@ -0,0 +1,53 @@
<template>
<div v-if="error != ''" class="ui negative message">
<div class="header"><i class="attention icon"></i> {{error}}</div>
</div>
<div v-else>
<div class="ui segments">
<div class="ui segment">
<p>任务<router-link class="item" :to="'/job/'+log.jobGroup+'/'+log.jobId">{{log.name}}</router-link></p>
</div>
<div class="ui segment">
<p>节点{{log.node}}</p>
</div>
<div class="ui segment">
<p>时间{{log.beginTime}} {{log.endTime}}</p>
</div>
</div>
<h4 class="ui header">执行的命令</h4>
<pre class="ui grey inverted segment">{{log.command}}</pre>
<h4 class="ui header">输出</h4>
<pre class="ui inverted segment">{{log.output}}</pre>
</div>
</template>
<script>
export default {
name: 'log-detail',
data: function(){
return {
log: {
id: 'sdfas',
jobId: 'wewe',
jobGroup: 'test',
name: 'run run run',
node: '192.168.1.2',
command: 'echo hello;',
output: 'hello',
exitCode: 0,
beginTime: new Date(),
endTime: new Date()
},
error: ''
}
},
mounted: function(){
var vm = this;
this.$rest.GET('log/'+this.$route.params.id).
onsucceed(200, (resp)=>{/*vm.log = resp*/}).
onfailed((data)=>{vm.error = data.error}).
do();
}
}
</script>

View File

@ -23,6 +23,7 @@ Vue.use(VueRouter);
import App from './App.vue';
import Dash from './components/Dash.vue';
import Log from './components/Log.vue';
import LogDetail from './components/LogDetail.vue';
import Job from './components/Job.vue';
import JobEdit from './components/JobEdit.vue';
import Node from './components/Node.vue';
@ -30,6 +31,7 @@ import Node from './components/Node.vue';
var routes = [
{path: '/', component: Dash},
{path: '/log', component: Log},
{path: '/log/:id', component: LogDetail},
{path: '/job', component: Job},
{path: '/job/create', component: JobEdit},
{path: '/job/edit/:group/:id', component: JobEdit},