完善任务列表、主机列表、任务日志列表

pull/21/merge
ouqiang 2017-04-08 17:15:30 +08:00
parent 2450d193f3
commit 2b0e66dd04
15 changed files with 434 additions and 19 deletions

View File

@ -5,4 +5,4 @@ gathering = explicit
; 默认模块
module_name = shell
host_key_checking = false
host_key_checking = false

View File

@ -49,6 +49,16 @@ func (host *Host) List() ([]Host, error) {
return list, err
}
func (host *Host) AllList() ([]Host, error) {
host.parsePageAndPageSize()
list := make([]Host, 0)
err := Db.Desc("id").Find(&list)
return list, err
}
func (host *Host) Total() (int64, error) {
return Db.Count(host)
}

View File

@ -5,7 +5,7 @@ package ansible
import (
"errors"
"github.com/ouqiang/gocron/modules/utils"
"github.com/ouqiang/gocron/modules/logger"
"strings"
)
// ansible是否有安装
@ -32,7 +32,15 @@ func ExecCommand(hosts string, hostFile string, module string, args ...string) (
if len(args) > 0 {
commandArgs = append(commandArgs, args...)
}
output, err = utils.ExecShell("ansible", commandArgs...)
retryTimes := 2
for i := 0; i < retryTimes; i++ {
output, err = utils.ExecShell("ansible", commandArgs...)
// todo 偶尔出现Failed to connect to the host via ssh. 暂时重试解决
if err == nil || !strings.HasPrefix(err.Error(), "exit status 3") {
break;
}
}
return
}

View File

@ -35,7 +35,7 @@ func (h *Hosts) GetFilename() string {
// 写入hosts
func (h *Hosts) Write() {
host := new(models.Host)
hostModels, err := host.List()
hostModels, err := host.AllList()
if err != nil {
logger.Error(err)
return

View File

@ -8,8 +8,19 @@ import (
"github.com/ouqiang/gocron/modules/ansible"
)
func Index(ctx *macaron.Context) {
hostModel := new(models.Host)
hosts, err := hostModel.List()
if err != nil {
logger.Error(err)
}
ctx.Data["Title"] = "主机列表"
ctx.Data["Hosts"] = hosts
ctx.HTML(200, "host/index")
}
func Create(ctx *macaron.Context) {
ctx.Data["Title"] = "主机管理"
ctx.Data["Title"] = "添加主机"
ctx.HTML(200, "host/create")
}

View File

@ -6,6 +6,7 @@ import (
"gopkg.in/macaron.v1"
"github.com/ouqiang/gocron/routers/task"
"github.com/ouqiang/gocron/routers/host"
"github.com/ouqiang/gocron/routers/tasklog"
)
// 路由注册
@ -14,7 +15,6 @@ func Register(m *macaron.Macaron) {
m.SetAutoHead(true)
// 404错误
m.NotFound(func(ctx *macaron.Context) {
// 判断是否get请求还是post, get返回页面, post返回json
ctx.HTML(404, "error/404")
})
// 50x错误
@ -40,17 +40,15 @@ func Register(m *macaron.Macaron) {
m.Group("/task", func() {
m.Get("/create", task.Create)
m.Post("/store", binding.Bind(task.TaskForm{}), task.Store)
m.Get("", task.Index)
m.Get("/log", tasklog.Index)
})
// 主机
m.Group("/host", func() {
m.Get("/create", host.Create)
m.Post("/store", binding.Bind(host.HostForm{}), host.Store)
})
// 任务日志
m.Group("/tasklog/", func() {
m.Get("", host.Index)
})
// API接口
@ -58,3 +56,17 @@ func Register(m *macaron.Macaron) {
})
}
func isAjaxRequest(ctx *macaron.Context) bool {
req := ctx.Req.Header.Get("X-Requested-With")
if req == "XMLHttpRequest" {
return true
}
return false
}
func isGetRequest(ctx *macaron.Context) bool {
return ctx.Req.Method == "GET"
}

View File

@ -9,6 +9,18 @@ import (
"github.com/ouqiang/gocron/service"
)
func Index(ctx *macaron.Context) {
taskModel := new(models.Task)
tasks, err := taskModel.List()
if err != nil {
logger.Error(err)
}
ctx.Data["Title"] = "任务列表"
ctx.Data["Tasks"] = tasks
ctx.Data["URI"] = "/task"
ctx.HTML(200, "task/index")
}
func Create(ctx *macaron.Context) {
hostModel := new(models.Host)
hosts, err := hostModel.List()
@ -16,9 +28,10 @@ func Create(ctx *macaron.Context) {
logger.Error(err)
ctx.Redirect("/host/create")
}
logger.Info(hosts)
logger.Debug(hosts)
ctx.Data["Title"] = "任务管理"
ctx.Data["Hosts"] = hosts
ctx.Data["URI"] = "/task/create"
ctx.Data["FirstHostName"] = hosts[0].Name
ctx.Data["FirstHostId"] = hosts[0].Id
ctx.HTML(200, "task/create")
@ -35,6 +48,7 @@ type TaskForm struct {
Remark string
}
// 保存任务
func Store(ctx *macaron.Context, form TaskForm) string {
hosts := ctx.Req.Form["hosts[]"]
taskModel := models.Task{}
@ -59,3 +73,8 @@ func Store(ctx *macaron.Context, form TaskForm) string {
return json.Success("保存成功", nil)
}
// 删除任务
func Remove(ctx *macaron.Context) {
}

View File

@ -0,0 +1,22 @@
package tasklog
import (
"gopkg.in/macaron.v1"
"github.com/ouqiang/gocron/models"
"github.com/ouqiang/gocron/modules/logger"
)
// @author qiang.ou<qingqianludao@gmail.com>
// @date 2017/4/7-21:18
func Index(ctx *macaron.Context) {
logModel := new(models.TaskLog)
logs, err := logModel.List()
if err != nil {
logger.Error(err)
}
ctx.Data["Title"] = "任务日志"
ctx.Data["Logs"] = logs
ctx.Data["URI"] = "/task/log"
ctx.HTML(200, "task/log")
}

View File

@ -172,8 +172,17 @@ func createHandlerJob(taskModel models.Task) cron.FuncJob {
logger.Error("写入任务日志失败-", err)
return
}
// err != nil 执行失败
result, err := handler.Run(taskModel)
// err != nil 执行失败, 失败重试3次
retryTime := 4
var result string = ""
for i := 0; i < retryTime; i++ {
result, err = handler.Run(taskModel)
if err == nil {
break
} else {
logger.Error("执行失败#tasklog.id-" + strconv.Itoa(taskLogId) + "#尝试次数-" + strconv.Itoa(i + 1) + "#" + err.Error() + " " + result)
}
}
_, err = updateTaskLog(int(taskLogId), result, err)
if err != nil {
logger.Error("更新任务日志失败-", err)

View File

@ -87,7 +87,7 @@
onSuccess: function(event, fields) {
var util = new Util();
util.post('/host/store', fields, function(code, message) {
location.reload();
location.href = "/host"
});
return false;

102
templates/host/index.html Normal file
View File

@ -0,0 +1,102 @@
{{{ template "common/header" . }}}
<div class="ui grid">
<!--the vertical menu-->
{{{ template "host/menu" . }}}
<div class="twelve wide column">
<div class="pageHeader">
<div class="segment">
<h3 class="ui dividing header">
<i class="large add icon"></i>
<div class="content">
添加主机
</div>
</h3>
</div>
</div>
<table class="ui single line table">
<thead>
<tr>
<th>主机名</th>
<th>别名</th>
<th>用户名</th>
<th>密码</th>
<th>端口</th>
<th>认证方式</th>
<th>备注</th>
</tr>
</thead>
<tbody>
{{{range $i, $v := .Hosts}}}
<tr>
<td>{{{.Name}}}</td>
<td>{{{.Alias}}}</td>
<td>{{{.Username}}}</td>
<td>{{{.Password}}}</td>
<td>{{{.Port}}}</td>
<td>{{{.LoginType}}}</td>
<td>{{{.Remark}}}</td>
</tr>
{{{end}}}
</tbody>
</table>
</div>
<!--the newDevice form-->
</div>
<script type="text/javascript">
$('.ui.form').form(
{
onSuccess: function(event, fields) {
var util = new Util();
util.post('/host/store', fields, function(code, message) {
location.reload();
});
return false;
},
fields: {
name: {
identifier : 'name',
rules: [
{
type : 'empty',
prompt : '请输入主机名'
}
]
},
alias: {
identifier : 'alias',
rules: [
{
type : 'empty',
prompt : '请输入主机别名'
}
]
},
username: {
identifier : 'username',
rules: [
{
type : 'empty',
prompt : '请输入SSH用户名'
}
]
},
port: {
identifier : 'port',
rules: [
{
type : 'integer',
prompt : '请输入SSH端口'
}
]
}
},
inline : true
});
</script>
{{{ template "common/footer" . }}}

View File

@ -107,6 +107,7 @@
onSuccess: function(event, fields) {
var util = new Util();
util.post('/task/store', fields, function(code, message) {
location.href = "/task"
});
return false;

109
templates/task/index.html Normal file
View File

@ -0,0 +1,109 @@
{{{ template "common/header" . }}}
<div class="ui grid">
<!--the vertical menu-->
{{{template "task/menu" .}}}
<div class="twelve wide column">
<div class="pageHeader">
<div class="segment">
<h3 class="ui dividing header">
<i class="tasks icon"></i>
<div class="content">
任务列表
</div>
</h3>
</div>
</div>
<table class="ui single line table">
<thead>
<tr>
<th>任务名称</th>
<th>cron表达式</th>
<th>协议</th>
<th>任务类型</th>
<th>命令</th>
<th>超时时间(秒)</th>
<th>延迟时间(秒)</th>
<th>主机</th>
<th>备注</th>
<th>状态</th>
</tr>
</thead>
<tbody>
{{{range $i, $v := .Tasks}}}
<tr>
<td>{{{.Name}}}</td>
<td>{{{.Spec}}}</td>
<td>{{{.Protocol}}}</td>
<td>{{{.Type}}}</td>
<td>{{{.Command}}}</td>
<td>{{{.Timeout}}}</td>
<td>{{{.Delay}}}</td>
<td>{{{.SshHosts}}}</td>
<td>{{{.Remark}}}</td>
<td>{{{.Status}}}</td>
</tr>
{{{end}}}
</tbody>
</table>
</div>
<!--the newDevice form-->
</div>
<script type="text/javascript">
$('.ui.checkbox')
.checkbox()
;
$('.ui.form').form(
{
onSuccess: function(event, fields) {
var util = new Util();
util.post('/task/store', fields, function(code, message) {
});
return false;
},
fields: {
name: {
identifier : 'name',
rules: [
{
type : 'empty',
prompt : '请输入任务名称'
}
]
},
spec: {
identifier : 'spec',
rules: [
{
type : 'empty',
prompt : '请输入crontab格式表达式'
}
]
},
command: {
identifier : 'command',
rules: [
{
type : 'empty',
prompt : '请输入任务命令'
}
]
}, hosts: {
identifier : 'hosts',
rules: [
{
type : 'checked',
prompt : '请选择主机'
}
]
}
},
inline : true
});
</script>
{{{ template "common/footer" . }}}

112
templates/task/log.html Normal file
View File

@ -0,0 +1,112 @@
{{{ template "common/header" . }}}
<div class="ui grid">
<!--the vertical menu-->
{{{ template "task/menu" . }}}
<div class="twelve wide column">
<div class="pageHeader">
<div class="segment">
<h3 class="ui dividing header">
<i class="large add icon"></i>
<div class="content">
任务日志
</div>
</h3>
</div>
</div>
<table class="ui single line table">
<thead>
<tr>
<th>任务名称</th>
<th>cron表达式</th>
<th>协议</th>
<th>任务类型</th>
<th>命令</th>
<th>超时时间(秒)</th>
<th>延迟时间(秒)</th>
<th>主机</th>
<th>开始时间</th>
<th>结束时间</th>
<th>状态</th>
<th>执行结果</th>
</tr>
</thead>
<tbody>
{{{range $i, $v := .Logs}}}
<tr>
<td>{{{.Name}}}</td>
<td>{{{.Spec}}}</td>
<td>{{{.Protocol}}}</td>
<td>{{{.Type}}}</td>
<td>{{{.Command}}}</td>
<td>{{{.Timeout}}}</td>
<td>{{{.Delay}}}</td>
<td>{{{.SshHosts}}}</td>
<td>{{{.StartTime}}}</td>
<td>{{{.EndTime}}}</td>
<td>{{{.Status}}}</td>
<td>{{{.Result}}}</td>
</tr>
{{{end}}}
</tbody>
</table>
</div>
<!--the newDevice form-->
</div>
<script type="text/javascript">
$('.ui.form').form(
{
onSuccess: function(event, fields) {
var util = new Util();
util.post('/host/store', fields, function(code, message) {
location.reload();
});
return false;
},
fields: {
name: {
identifier : 'name',
rules: [
{
type : 'empty',
prompt : '请输入主机名'
}
]
},
alias: {
identifier : 'alias',
rules: [
{
type : 'empty',
prompt : '请输入主机别名'
}
]
},
username: {
identifier : 'username',
rules: [
{
type : 'empty',
prompt : '请输入SSH用户名'
}
]
},
port: {
identifier : 'port',
rules: [
{
type : 'integer',
prompt : '请输入SSH端口'
}
]
}
},
inline : true
});
</script>
{{{ template "common/footer" . }}}

View File

@ -1,13 +1,13 @@
<div class="four wide column">
<div class="three wide column">
<div class="verticalMenu">
<div class="ui vertical pointing menu fluid">
<a class="active teal item" href="/task">
<a class="{{{if eq .URI "/task"}}}active teal{{{end}}} item" href="/task">
<i class="tasks icon"></i> 任务列表
</a>
<a class="item" href="/task/create">
<a class="item {{{if eq .URI "/task/create"}}}active teal{{{end}}} " href="/task/create">
<i class="plus icon"></i> 添加任务
</a>
<a class="item" href="">
<a class="item {{{if eq .URI "/task/log"}}}active teal{{{end}}} " href="/task/log">
<i class="bar chart icon"></i> 任务日志
</a>
</div>