mirror of https://github.com/ouqiang/gocron
增加slack消息通知
parent
bafafadae1
commit
a780076a57
|
@ -5,8 +5,6 @@ import (
|
||||||
"github.com/go-xorm/xorm"
|
"github.com/go-xorm/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 主机
|
// 主机
|
||||||
type Host struct {
|
type Host struct {
|
||||||
Id int16 `xorm:"smallint pk autoincr"`
|
Id int16 `xorm:"smallint pk autoincr"`
|
||||||
|
|
|
@ -12,8 +12,9 @@ func (migration *Migration) Exec(dbName string) error {
|
||||||
if !isDatabaseExist(dbName) {
|
if !isDatabaseExist(dbName) {
|
||||||
return errors.New("数据库不存在")
|
return errors.New("数据库不存在")
|
||||||
}
|
}
|
||||||
|
setting := new(Setting)
|
||||||
tables := []interface{}{
|
tables := []interface{}{
|
||||||
&User{}, &Task{}, &TaskLog{}, &Host{},
|
&User{}, &Task{}, &TaskLog{}, &Host{}, setting,
|
||||||
}
|
}
|
||||||
for _, table := range tables {
|
for _, table := range tables {
|
||||||
exist, err:= Db.IsTableExist(table)
|
exist, err:= Db.IsTableExist(table)
|
||||||
|
@ -28,6 +29,10 @@ func (migration *Migration) Exec(dbName string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
err := setting.InitBasicField()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type Setting struct {
|
||||||
|
Id int16 `xorm:"smallint pk autoincr"`
|
||||||
|
Code string `xorm:"varchar(32) notnull"`
|
||||||
|
Key string `xorm:"varchar(64) notnull"`
|
||||||
|
Value string `xorm:"varchar(4096) notnull default '' "`
|
||||||
|
}
|
||||||
|
|
||||||
|
const SlackCode = "slack"
|
||||||
|
const SlackKey = "url"
|
||||||
|
|
||||||
|
// 初始化基本字段 邮件、slack等
|
||||||
|
func (setting *Setting) InitBasicField() (error) {
|
||||||
|
setting.Code = "slack";
|
||||||
|
setting.Key = "url"
|
||||||
|
setting.Value = ""
|
||||||
|
_, err := Db.Insert(setting)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (setting *Setting) SlackUrl() (string, error) {
|
||||||
|
setting.slackCondition()
|
||||||
|
_, err := Db.Get(setting)
|
||||||
|
|
||||||
|
return setting.Value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (setting *Setting) UpdateSlackUrl(url string) (int64, error) {
|
||||||
|
setting.slackCondition()
|
||||||
|
setting.Value = url
|
||||||
|
|
||||||
|
return setting.UpdateBean()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (setting *Setting) slackCondition() {
|
||||||
|
setting.Code = SlackCode
|
||||||
|
setting.Key = SlackKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (setting *Setting) UpdateBean() (int64, error) {
|
||||||
|
return Db.Cols("code,key,value").Update(setting)
|
||||||
|
}
|
|
@ -58,7 +58,7 @@ func (task *Task) Create() (insertId int, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (task *Task) UpdateBean(id int) (int64, error) {
|
func (task *Task) UpdateBean(id int) (int64, error) {
|
||||||
return Db.ID(id).UseBool("status,multi").Update(task)
|
return Db.ID(id).Cols("name,spec,protocol,command,timeout,multi,retry_times,host_id,remark,status").Update(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新
|
// 更新
|
||||||
|
@ -91,7 +91,7 @@ func (task *Task) ActiveList() ([]TaskHost, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取某个主机下的所有激活任务
|
// 获取某个主机下的所有激活任务
|
||||||
func (task *Task) ActiveListByHostId(hostId int16) ([]TaskHost, error) {
|
func (task *Task) ActiveListByHostId(hostId int16) ([]TaskHost, error) {
|
||||||
list := make([]TaskHost, 0)
|
list := make([]TaskHost, 0)
|
||||||
fields := "t.*, host.alias,host.name,host.username,host.password,host.port,host.auth_type,host.private_key"
|
fields := "t.*, host.alias,host.name,host.username,host.password,host.port,host.auth_type,host.private_key"
|
||||||
err := Db.Alias("t").Join("LEFT", hostTableName(), "t.host_id=host.id").Where("t.status = ? AND t.host_id = ?", Enabled, hostId).Cols(fields).Find(&list)
|
err := Db.Alias("t").Join("LEFT", hostTableName(), "t.host_id=host.id").Where("t.status = ? AND t.host_id = ?", Enabled, hostId).Cols(fields).Find(&list)
|
||||||
|
|
|
@ -22,7 +22,7 @@ type TaskLog struct {
|
||||||
StartTime time.Time `xorm:"datetime created"` // 开始执行时间
|
StartTime time.Time `xorm:"datetime created"` // 开始执行时间
|
||||||
EndTime time.Time `xorm:"datetime updated"` // 执行完成(失败)时间
|
EndTime time.Time `xorm:"datetime updated"` // 执行完成(失败)时间
|
||||||
Status Status `xorm:"tinyint notnull default 1"` // 状态 0:执行失败 1:执行中 2:执行完毕 3:任务取消(上次任务未执行完成)
|
Status Status `xorm:"tinyint notnull default 1"` // 状态 0:执行失败 1:执行中 2:执行完毕 3:任务取消(上次任务未执行完成)
|
||||||
Result string `xorm:"text notnull defalut '' "` // 执行结果
|
Result string `xorm:"mediumtext notnull defalut '' "` // 执行结果
|
||||||
TotalTime int `xorm:"-"` // 执行总时长
|
TotalTime int `xorm:"-"` // 执行总时长
|
||||||
BaseModel `xorm:"-"`
|
BaseModel `xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/ouqiang/gocron/modules/setting"
|
"github.com/ouqiang/gocron/modules/setting"
|
||||||
"github.com/ouqiang/gocron/modules/logger"
|
"github.com/ouqiang/gocron/modules/logger"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"github.com/ouqiang/gocron/modules/notify"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -36,6 +37,8 @@ func InitEnv() {
|
||||||
if Installed {
|
if Installed {
|
||||||
InitDb()
|
InitDb()
|
||||||
InitResource()
|
InitResource()
|
||||||
|
settingModel := new(models.Setting)
|
||||||
|
notify.SlackUrl, _ = settingModel.SlackUrl()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
package httpclient
|
||||||
|
|
||||||
|
// http-client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
"fmt"
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResponseWrapper struct {
|
||||||
|
StatusCode int
|
||||||
|
Body string
|
||||||
|
Header http.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(url string, timeout int) ResponseWrapper {
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return createRequestError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return request(req, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostBody(url string, body string, timeout int) ResponseWrapper {
|
||||||
|
buf := bytes.NewBufferString(body)
|
||||||
|
req, err := http.NewRequest("POST", url, buf)
|
||||||
|
if err != nil {
|
||||||
|
return createRequestError(err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-type", "application/json")
|
||||||
|
|
||||||
|
return request(req, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func request(req *http.Request, timeout int) ResponseWrapper {
|
||||||
|
wrapper := ResponseWrapper{StatusCode: 0, Body: "", Header: make(http.Header)}
|
||||||
|
client := &http.Client{}
|
||||||
|
if timeout > 0 {
|
||||||
|
client.Timeout = time.Duration(timeout) * time.Second
|
||||||
|
}
|
||||||
|
setRequestHeader(req)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
wrapper.Body = fmt.Sprintf("执行HTTP请求错误-%s", err.Error())
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
wrapper.Body = fmt.Sprintf("读取HTTP请求返回值失败-%s", err.Error())
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
|
wrapper.StatusCode = resp.StatusCode
|
||||||
|
wrapper.Body = string(body)
|
||||||
|
wrapper.Header = resp.Header
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func setRequestHeader(req *http.Request) {
|
||||||
|
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.8,en;q=0.6")
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 golang/gocron")
|
||||||
|
}
|
||||||
|
|
||||||
|
func createRequestError(err error) ResponseWrapper {
|
||||||
|
errorMessage := fmt.Sprintf("创建HTTP请求错误-%s", err.Error())
|
||||||
|
return ResponseWrapper{0, errorMessage, make(http.Header)}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package notify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var SlackUrl string
|
||||||
|
|
||||||
|
type Message map[string]interface{}
|
||||||
|
type Notifiable interface {
|
||||||
|
send(msg Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
var queue chan Message = make(chan Message, 100)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
go run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把消息推入队列
|
||||||
|
func Push(msg Message) {
|
||||||
|
queue <- msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() {
|
||||||
|
slack := new(Slack)
|
||||||
|
for msg := range queue {
|
||||||
|
// 根据任务配置执行相应通知
|
||||||
|
go slack.Send(msg)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package notify
|
||||||
|
// 发送消息到slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"github.com/ouqiang/gocron/modules/httpclient"
|
||||||
|
"github.com/ouqiang/gocron/modules/logger"
|
||||||
|
"github.com/ouqiang/gocron/modules/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Slack struct {}
|
||||||
|
|
||||||
|
func (slack *Slack) Send(msg Message) {
|
||||||
|
name, nameOk := msg["name"]
|
||||||
|
statusName, statusOk := msg["status"]
|
||||||
|
content, contentOk := msg["output"]
|
||||||
|
if SlackUrl == "" {
|
||||||
|
logger.Error("slack#webhooks-url为空")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !nameOk || !statusOk || !contentOk {
|
||||||
|
logger.Error("slack#消息字段不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body := fmt.Sprintf("============\n============\n============\n任务名称: %s\n状态: %s\n输出:\n %s\n", name, statusName, content)
|
||||||
|
formatBody := slack.format(body)
|
||||||
|
timeout := 30
|
||||||
|
maxTimes := 3
|
||||||
|
i := 0
|
||||||
|
for i < maxTimes {
|
||||||
|
resp := httpclient.PostBody(SlackUrl, formatBody, timeout)
|
||||||
|
if resp.StatusCode == 200 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
if i < maxTimes {
|
||||||
|
logger.Error("slack#发送消息失败#%s#消息内容-%s", resp.Body, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化消息内容
|
||||||
|
func (slack *Slack) format(content string) string {
|
||||||
|
content = utils.EscapeJson(content)
|
||||||
|
specialChars := []string{"&", "<", ">"}
|
||||||
|
replaceChars := []string{"&", "<", ">"}
|
||||||
|
for i, v := range specialChars {
|
||||||
|
content = strings.Replace(content, v, replaceChars[i], 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(`{"text":"%s","username":"监控"}`, content)
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"runtime"
|
"runtime"
|
||||||
"github.com/Tang-RoseChild/mahonia"
|
"github.com/Tang-RoseChild/mahonia"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,3 +74,14 @@ func GBK2UTF8(s string) (string, bool) {
|
||||||
|
|
||||||
return dec.ConvertStringOK(s)
|
return dec.ConvertStringOK(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 转义json特殊字符
|
||||||
|
func EscapeJson(s string) string {
|
||||||
|
specialChars := []string{"\\", "\b","\f", "\n", "\r", "\t", "\"",}
|
||||||
|
replaceChars := []string{ "\\\\", "\\b", "\\f", "\\n", "\\r", "\\t", "\\\"",}
|
||||||
|
for i, v := range specialChars {
|
||||||
|
s = strings.Replace(s, v, replaceChars[i], 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
|
@ -21,9 +21,10 @@ type InstallForm struct {
|
||||||
DbUsername string `binding:"Required;MaxSize(50)"`
|
DbUsername string `binding:"Required;MaxSize(50)"`
|
||||||
DbPassword string `binding:"Required;MaxSize(30)"`
|
DbPassword string `binding:"Required;MaxSize(30)"`
|
||||||
DbName string `binding:"Required;MaxSize(50)"`
|
DbName string `binding:"Required;MaxSize(50)"`
|
||||||
DbTablePrefix string `binding:"MinSize(20)"`
|
DbTablePrefix string `binding:"MaxSize(20)"`
|
||||||
AdminUsername string `binding:"Required;MaxSize(3)"`
|
AdminUsername string `binding:"Required;MinSize(3)"`
|
||||||
AdminPassword string `binding:"Required;MaxSize(6)"`
|
AdminPassword string `binding:"Required;MinSize(6)"`
|
||||||
|
ConfirmAdminPassword string `binding:"Required;MinSize(6)"`
|
||||||
AdminEmail string `binding:"Required;Email;MaxSize(50)"`
|
AdminEmail string `binding:"Required;Email;MaxSize(50)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +47,9 @@ func Store(ctx *macaron.Context, form InstallForm) string {
|
||||||
if app.Installed {
|
if app.Installed {
|
||||||
return json.CommonFailure("系统已安装!")
|
return json.CommonFailure("系统已安装!")
|
||||||
}
|
}
|
||||||
|
if form.AdminPassword != form.ConfirmAdminPassword {
|
||||||
|
return json.CommonFailure("两次输入密码不匹配")
|
||||||
|
}
|
||||||
err := testDbConnection(form)
|
err := testDbConnection(form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return json.CommonFailure("数据库连接失败", err)
|
return json.CommonFailure("数据库连接失败", err)
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/ouqiang/gocron/modules/logger"
|
"github.com/ouqiang/gocron/modules/logger"
|
||||||
"github.com/ouqiang/gocron/routers/user"
|
"github.com/ouqiang/gocron/routers/user"
|
||||||
"github.com/go-macaron/gzip"
|
"github.com/go-macaron/gzip"
|
||||||
|
"github.com/ouqiang/gocron/routers/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 静态文件目录
|
// 静态文件目录
|
||||||
|
@ -64,6 +65,14 @@ func Register(m *macaron.Macaron) {
|
||||||
m.Post("/remove/:id", host.Remove)
|
m.Post("/remove/:id", host.Remove)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 管理
|
||||||
|
m.Group("/admin", func() {
|
||||||
|
m.Group("/setting/", func() {
|
||||||
|
m.Get("/slack", setting.EditSlack)
|
||||||
|
|
||||||
|
})
|
||||||
|
}, adminAuth)
|
||||||
|
|
||||||
// 404错误
|
// 404错误
|
||||||
m.NotFound(func(ctx *macaron.Context) {
|
m.NotFound(func(ctx *macaron.Context) {
|
||||||
if isGetRequest(ctx) && !isAjaxRequest(ctx) {
|
if isGetRequest(ctx) && !isAjaxRequest(ctx) {
|
||||||
|
@ -116,7 +125,6 @@ func RegisterMiddleware(m *macaron.Macaron) {
|
||||||
setShareData(m)
|
setShareData(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 系统未安装,重定向到安装页面
|
// 系统未安装,重定向到安装页面
|
||||||
func checkAppInstall(m *macaron.Macaron) {
|
func checkAppInstall(m *macaron.Macaron) {
|
||||||
m.Use(func(ctx *macaron.Context) {
|
m.Use(func(ctx *macaron.Context) {
|
||||||
|
@ -170,6 +178,14 @@ func setShareData(m *macaron.Macaron) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 管理员认证
|
||||||
|
func adminAuth(ctx *macaron.Context, sess session.Store) {
|
||||||
|
if !user.IsAdmin(sess) {
|
||||||
|
ctx.Data["Title"] = "无权限访问此页面"
|
||||||
|
ctx.HTML(403, "error/no_permission")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func isAjaxRequest(ctx *macaron.Context) bool {
|
func isAjaxRequest(ctx *macaron.Context) bool {
|
||||||
req := ctx.Req.Header.Get("X-Requested-With")
|
req := ctx.Req.Header.Get("X-Requested-With")
|
||||||
if req == "XMLHttpRequest" {
|
if req == "XMLHttpRequest" {
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopkg.in/macaron.v1"
|
||||||
|
"github.com/ouqiang/gocron/modules/utils"
|
||||||
|
"github.com/ouqiang/gocron/models"
|
||||||
|
"github.com/ouqiang/gocron/modules/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EditSlack(ctx *macaron.Context) {
|
||||||
|
ctx.Data["Title"] = "slack配置"
|
||||||
|
settingModel := new(models.Setting)
|
||||||
|
url, err := settingModel.SlackUrl()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
ctx.Data["SlackUrl"] = url
|
||||||
|
ctx.HTML(200, "setting/slack")
|
||||||
|
}
|
||||||
|
|
||||||
|
func StoreSlack(ctx *macaron.Context) string {
|
||||||
|
url := ctx.QueryTrim("url")
|
||||||
|
settingModel := new(models.Setting)
|
||||||
|
_, err := settingModel.UpdateSlackUrl(url)
|
||||||
|
json := utils.JsonResponse{}
|
||||||
|
if err != nil {
|
||||||
|
return json.CommonFailure(utils.FailureContent, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Success(utils.SuccessContent, nil)
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ func ValidateLogin(ctx *macaron.Context, sess session.Store) string {
|
||||||
|
|
||||||
sess.Set("username", userModel.Name)
|
sess.Set("username", userModel.Name)
|
||||||
sess.Set("uid", userModel.Id)
|
sess.Set("uid", userModel.Id)
|
||||||
|
sess.Set("isAdmin", userModel.IsAdmin)
|
||||||
|
|
||||||
return json.Success("登录成功", nil)
|
return json.Success("登录成功", nil)
|
||||||
}
|
}
|
||||||
|
@ -72,3 +73,12 @@ func IsLogin(sess session.Store) bool {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsAdmin(sess session.Store) bool {
|
||||||
|
isAdmin, ok := sess.Get("isAdmin").(int8)
|
||||||
|
if ok && isAdmin > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
|
@ -2,8 +2,6 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ouqiang/gocron/models"
|
"github.com/ouqiang/gocron/models"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
"github.com/ouqiang/gocron/modules/logger"
|
"github.com/ouqiang/gocron/modules/logger"
|
||||||
|
@ -12,12 +10,14 @@ import (
|
||||||
"github.com/ouqiang/gocron/modules/utils"
|
"github.com/ouqiang/gocron/modules/utils"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ouqiang/gocron/modules/httpclient"
|
||||||
|
"github.com/ouqiang/gocron/modules/notify"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Cron *cron.Cron
|
var Cron *cron.Cron
|
||||||
var runInstance Instance
|
var runInstance Instance
|
||||||
|
|
||||||
// 任务ID作为Key, 不会出现并发读写, 不加锁
|
// 任务ID作为Key, 不会出现并发写, 不加锁
|
||||||
type Instance struct {
|
type Instance struct {
|
||||||
Status map[int]bool
|
Status map[int]bool
|
||||||
}
|
}
|
||||||
|
@ -136,35 +136,13 @@ func (h *LocalCommandHandler) runOnUnix(taskModel models.TaskHost) (string, erro
|
||||||
type HTTPHandler struct{}
|
type HTTPHandler struct{}
|
||||||
|
|
||||||
func (h *HTTPHandler) Run(taskModel models.TaskHost) (result string, err error) {
|
func (h *HTTPHandler) Run(taskModel models.TaskHost) (result string, err error) {
|
||||||
client := &http.Client{}
|
resp := httpclient.Get(taskModel.Command, taskModel.Timeout)
|
||||||
if taskModel.Timeout > 0 {
|
|
||||||
client.Timeout = time.Duration(taskModel.Timeout) * time.Second
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest("GET", taskModel.Command, nil)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("任务处理#创建HTTP请求错误-", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-type", "application/x-www-form-urlencoded")
|
|
||||||
req.Header.Set("User-Agent", "golang/gocron")
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("任务处理HTTP请求错误-", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("任务处理#读取HTTP请求返回值失败-", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 返回状态码非200,均为失败
|
// 返回状态码非200,均为失败
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
return string(body), errors.New(fmt.Sprintf("HTTP状态码非200-->%d", resp.StatusCode))
|
return resp.Body, errors.New(fmt.Sprintf("HTTP状态码非200-->%d", resp.StatusCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(body), err
|
return resp.Body, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSH-command任务
|
// SSH-command任务
|
||||||
|
@ -208,7 +186,7 @@ func updateTaskLog(taskLogId int64, taskResult TaskResult) (int64, error) {
|
||||||
var status models.Status
|
var status models.Status
|
||||||
var result string = taskResult.Result
|
var result string = taskResult.Result
|
||||||
if taskResult.Err != nil {
|
if taskResult.Err != nil {
|
||||||
result = taskResult.Err.Error() + " " + result
|
result = taskResult.Err.Error() + "\n" + result
|
||||||
status = models.Failure
|
status = models.Failure
|
||||||
} else {
|
} else {
|
||||||
status = models.Finish
|
status = models.Finish
|
||||||
|
@ -241,10 +219,27 @@ func createJob(taskModel models.TaskHost) cron.FuncJob {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
taskResult := execJob(handler, taskModel)
|
taskResult := execJob(handler, taskModel)
|
||||||
|
if taskResult.Err != nil {
|
||||||
|
taskResult.Result = taskResult.Err.Error() + "\n" + taskResult.Result
|
||||||
|
}
|
||||||
_, err = updateTaskLog(taskLogId, taskResult)
|
_, err = updateTaskLog(taskLogId, taskResult)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("任务结束#更新任务日志失败-", err)
|
logger.Error("任务结束#更新任务日志失败-", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var statusName string
|
||||||
|
if taskResult.Err != nil {
|
||||||
|
statusName = "失败"
|
||||||
|
} else {
|
||||||
|
statusName = "成功"
|
||||||
|
}
|
||||||
|
msg := notify.Message{
|
||||||
|
"name": taskModel.Task.Name,
|
||||||
|
"output": taskResult.Result,
|
||||||
|
"status": statusName,
|
||||||
|
"taskId": taskModel.Id,
|
||||||
|
};
|
||||||
|
notify.Push(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
return taskFunc
|
return taskFunc
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
<a class="item {{{if eq .Controller "host"}}}active{{{end}}}" href="/host"><i class="linux icon"></i>主机</a>
|
<a class="item {{{if eq .Controller "host"}}}active{{{end}}}" href="/host"><i class="linux icon"></i>主机</a>
|
||||||
<!-- <a class="item {{{if eq .Controller "user"}}}active{{{end}}}" href="/user"><i class="user icon"></i>账户</a> -->
|
<!-- <a class="item {{{if eq .Controller "user"}}}active{{{end}}}" href="/user"><i class="user icon"></i>账户</a> -->
|
||||||
{{{if gt .LoginUid 0}}}
|
{{{if gt .LoginUid 0}}}
|
||||||
<a class="item {{{if eq .Controller "admin"}}}active{{{end}}}" href="/admin"><i class="settings icon"></i>管理</a>
|
<a class="item {{{if eq .Controller "admin"}}}active{{{end}}}" href="/admin/setting/slack"><i class="settings icon"></i>管理</a>
|
||||||
{{{end}}}
|
{{{end}}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
{{{ template "common/header" . }}}
|
||||||
|
<script>
|
||||||
|
swal({
|
||||||
|
title: "403 - FORBIDDEN",
|
||||||
|
text: "您无权限访问此页面",
|
||||||
|
type: "warning"
|
||||||
|
},
|
||||||
|
function(){
|
||||||
|
location.href = "/"
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{{ template "common/footer" . }}}
|
|
@ -48,7 +48,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>密码</label>
|
<label>密码</label>
|
||||||
<input type="text" name="db_password">
|
<input type="password" name="db_password">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="three fields">
|
<div class="three fields">
|
||||||
|
@ -75,7 +75,13 @@
|
||||||
<div class="three fields">
|
<div class="three fields">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>管理员密码</label>
|
<label>管理员密码</label>
|
||||||
<input type="text" name="admin_password">
|
<input type="password" name="admin_password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="three fields">
|
||||||
|
<div class="field">
|
||||||
|
<label>确认管理员密码</label>
|
||||||
|
<input type="password" name="confirm_admin_password">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="three fields">
|
<div class="three fields">
|
||||||
|
@ -164,6 +170,15 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
confirmAdminPassword: {
|
||||||
|
identifier : 'confirm_admin_password',
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
type : 'match[admin_password]',
|
||||||
|
prompt : '两次输入密码不匹配'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
adminEmail: {
|
adminEmail: {
|
||||||
identifier : 'admin_email',
|
identifier : 'admin_email',
|
||||||
rules: [
|
rules: [
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<div class="three wide column">
|
||||||
|
<div class="verticalMenu">
|
||||||
|
<div class="ui vertical pointing menu fluid">
|
||||||
|
<a class="{{{if eq .URI "/admin/setting/slack"}}}active teal{{{end}}} item" href="/admin/setting/slack">
|
||||||
|
<i class="slack icon"></i> Slack配置
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,43 @@
|
||||||
|
{{{ template "common/header" . }}}
|
||||||
|
<div class="ui grid">
|
||||||
|
{{{template "setting/menu" .}}}
|
||||||
|
<div class="twelve wide column">
|
||||||
|
<div class="pageHeader">
|
||||||
|
<div class="segment">
|
||||||
|
<h3 class="ui dividing header">
|
||||||
|
<div class="content">
|
||||||
|
{{{.Title}}}
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form class="ui form fluid vertical segment">
|
||||||
|
<div class="two fields">
|
||||||
|
<div class="field">
|
||||||
|
<label>
|
||||||
|
<div class="content">Slack WebHook URL</div>
|
||||||
|
</label>
|
||||||
|
<div class="ui small input">
|
||||||
|
<input type="text" name="url" value="{{{.SlackUrl}}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui primary submit button">保存</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var $uiForm = $('.ui.form');
|
||||||
|
$($uiForm).form(
|
||||||
|
{
|
||||||
|
onSuccess: function(event, fields) {
|
||||||
|
util.post('/admin/setting/slack', fields, function(code, message) {
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
{{{ template "common/footer" . }}}
|
|
@ -41,6 +41,7 @@
|
||||||
<option value="1" {{{if eq .Params.Status 0}}}selected{{{end}}} >失败</option>
|
<option value="1" {{{if eq .Params.Status 0}}}selected{{{end}}} >失败</option>
|
||||||
<option value="2" {{{if eq .Params.Status 1}}}selected{{{end}}}>执行中</option>
|
<option value="2" {{{if eq .Params.Status 1}}}selected{{{end}}}>执行中</option>
|
||||||
<option value="3" {{{if eq .Params.Status 2}}}selected{{{end}}}>成功</option>
|
<option value="3" {{{if eq .Params.Status 2}}}selected{{{end}}}>成功</option>
|
||||||
|
<option value="4" {{{if eq .Params.Status 3}}}selected{{{end}}}>取消</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
|
|
@ -69,7 +69,7 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="four fields" id="hostField">
|
<div class="three fields" id="hostField">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>主机</label>
|
<label>主机</label>
|
||||||
<div class="ui blue message">
|
<div class="ui blue message">
|
||||||
|
@ -81,7 +81,8 @@
|
||||||
<option value="{{{.Id}}}" {{{if $.Task}}}{{{if eq $.Task.HostId .Id }}} selected {{{end}}} {{{end}}}>{{{.Alias}}}-{{{.Name}}}</option>
|
<option value="{{{.Id}}}" {{{if $.Task}}}{{{if eq $.Task.HostId .Id }}} selected {{{end}}} {{{end}}}>{{{.Alias}}}-{{{.Name}}}</option>
|
||||||
{{{end}}}
|
{{{end}}}
|
||||||
</select> <a class="ui blue button" href="/host/create">添加主机</a>
|
</select> <a class="ui blue button" href="/host/create">添加主机</a>
|
||||||
</div>
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="two fields">
|
<div class="two fields">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -112,7 +113,7 @@
|
||||||
重试时间间隔 重试次数 * 分钟, 按1分钟、2分钟、3分钟.....的间隔进行重试<br>
|
重试时间间隔 重试次数 * 分钟, 按1分钟、2分钟、3分钟.....的间隔进行重试<br>
|
||||||
取值范围1-10, 默认0,不重试
|
取值范围1-10, 默认0,不重试
|
||||||
</div>
|
</div>
|
||||||
<input type="text" name="timeout" value="{{{.Task.Timeout}}}">
|
<input type="text" name="retry_times" value="{{{.Task.RetryTimes}}}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="three fields">
|
<div class="three fields">
|
||||||
|
|
Loading…
Reference in New Issue