mirror of https://github.com/shunfei/cronsun
日志显示列表和日志详细信息界面
parent
5b4900d6c4
commit
fefb89c9cb
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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' ? '添加' : '更新'}}任务
|
||||
<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)=>{
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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},
|
||||
|
|
Loading…
Reference in New Issue