mirror of https://github.com/ouqiang/gocron
增加slack消息通知
parent
bafafadae1
commit
a780076a57
|
@ -5,8 +5,6 @@ import (
|
|||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
|
||||
|
||||
// 主机
|
||||
type Host struct {
|
||||
Id int16 `xorm:"smallint pk autoincr"`
|
||||
|
|
|
@ -12,8 +12,9 @@ func (migration *Migration) Exec(dbName string) error {
|
|||
if !isDatabaseExist(dbName) {
|
||||
return errors.New("数据库不存在")
|
||||
}
|
||||
setting := new(Setting)
|
||||
tables := []interface{}{
|
||||
&User{}, &Task{}, &TaskLog{}, &Host{},
|
||||
&User{}, &Task{}, &TaskLog{}, &Host{}, setting,
|
||||
}
|
||||
for _, table := range tables {
|
||||
exist, err:= Db.IsTableExist(table)
|
||||
|
@ -28,6 +29,10 @@ func (migration *Migration) Exec(dbName string) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
err := setting.InitBasicField()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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) {
|
||||
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)
|
||||
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)
|
||||
|
|
|
@ -22,7 +22,7 @@ type TaskLog struct {
|
|||
StartTime time.Time `xorm:"datetime created"` // 开始执行时间
|
||||
EndTime time.Time `xorm:"datetime updated"` // 执行完成(失败)时间
|
||||
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:"-"` // 执行总时长
|
||||
BaseModel `xorm:"-"`
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/ouqiang/gocron/modules/setting"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"runtime"
|
||||
"github.com/ouqiang/gocron/modules/notify"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -36,6 +37,8 @@ func InitEnv() {
|
|||
if Installed {
|
||||
InitDb()
|
||||
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"
|
||||
"runtime"
|
||||
"github.com/Tang-RoseChild/mahonia"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
|
@ -72,4 +73,15 @@ func GBK2UTF8(s string) (string, bool) {
|
|||
dec := mahonia.NewDecoder("gbk")
|
||||
|
||||
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)"`
|
||||
DbPassword string `binding:"Required;MaxSize(30)"`
|
||||
DbName string `binding:"Required;MaxSize(50)"`
|
||||
DbTablePrefix string `binding:"MinSize(20)"`
|
||||
AdminUsername string `binding:"Required;MaxSize(3)"`
|
||||
AdminPassword string `binding:"Required;MaxSize(6)"`
|
||||
DbTablePrefix string `binding:"MaxSize(20)"`
|
||||
AdminUsername string `binding:"Required;MinSize(3)"`
|
||||
AdminPassword string `binding:"Required;MinSize(6)"`
|
||||
ConfirmAdminPassword string `binding:"Required;MinSize(6)"`
|
||||
AdminEmail string `binding:"Required;Email;MaxSize(50)"`
|
||||
}
|
||||
|
||||
|
@ -46,6 +47,9 @@ func Store(ctx *macaron.Context, form InstallForm) string {
|
|||
if app.Installed {
|
||||
return json.CommonFailure("系统已安装!")
|
||||
}
|
||||
if form.AdminPassword != form.ConfirmAdminPassword {
|
||||
return json.CommonFailure("两次输入密码不匹配")
|
||||
}
|
||||
err := testDbConnection(form)
|
||||
if err != nil {
|
||||
return json.CommonFailure("数据库连接失败", err)
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/ouqiang/gocron/routers/user"
|
||||
"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.Group("/admin", func() {
|
||||
m.Group("/setting/", func() {
|
||||
m.Get("/slack", setting.EditSlack)
|
||||
|
||||
})
|
||||
}, adminAuth)
|
||||
|
||||
// 404错误
|
||||
m.NotFound(func(ctx *macaron.Context) {
|
||||
if isGetRequest(ctx) && !isAjaxRequest(ctx) {
|
||||
|
@ -116,7 +125,6 @@ func RegisterMiddleware(m *macaron.Macaron) {
|
|||
setShareData(m)
|
||||
}
|
||||
|
||||
|
||||
// 系统未安装,重定向到安装页面
|
||||
func checkAppInstall(m *macaron.Macaron) {
|
||||
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 {
|
||||
req := ctx.Req.Header.Get("X-Requested-With")
|
||||
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("uid", userModel.Id)
|
||||
sess.Set("isAdmin", userModel.IsAdmin)
|
||||
|
||||
return json.Success("登录成功", nil)
|
||||
}
|
||||
|
@ -70,5 +71,14 @@ func IsLogin(sess session.Store) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
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 (
|
||||
"github.com/ouqiang/gocron/models"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
|
@ -12,12 +10,14 @@ import (
|
|||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ouqiang/gocron/modules/httpclient"
|
||||
"github.com/ouqiang/gocron/modules/notify"
|
||||
)
|
||||
|
||||
var Cron *cron.Cron
|
||||
var runInstance Instance
|
||||
|
||||
// 任务ID作为Key, 不会出现并发读写, 不加锁
|
||||
// 任务ID作为Key, 不会出现并发写, 不加锁
|
||||
type Instance struct {
|
||||
Status map[int]bool
|
||||
}
|
||||
|
@ -136,35 +136,13 @@ func (h *LocalCommandHandler) runOnUnix(taskModel models.TaskHost) (string, erro
|
|||
type HTTPHandler struct{}
|
||||
|
||||
func (h *HTTPHandler) Run(taskModel models.TaskHost) (result string, err error) {
|
||||
client := &http.Client{}
|
||||
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
|
||||
}
|
||||
resp := httpclient.Get(taskModel.Command, taskModel.Timeout)
|
||||
// 返回状态码非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任务
|
||||
|
@ -208,7 +186,7 @@ func updateTaskLog(taskLogId int64, taskResult TaskResult) (int64, error) {
|
|||
var status models.Status
|
||||
var result string = taskResult.Result
|
||||
if taskResult.Err != nil {
|
||||
result = taskResult.Err.Error() + " " + result
|
||||
result = taskResult.Err.Error() + "\n" + result
|
||||
status = models.Failure
|
||||
} else {
|
||||
status = models.Finish
|
||||
|
@ -241,10 +219,27 @@ func createJob(taskModel models.TaskHost) cron.FuncJob {
|
|||
return
|
||||
}
|
||||
taskResult := execJob(handler, taskModel)
|
||||
if taskResult.Err != nil {
|
||||
taskResult.Result = taskResult.Err.Error() + "\n" + taskResult.Result
|
||||
}
|
||||
_, err = updateTaskLog(taskLogId, taskResult)
|
||||
if err != nil {
|
||||
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
|
||||
|
|
|
@ -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 "user"}}}active{{{end}}}" href="/user"><i class="user icon"></i>账户</a> -->
|
||||
{{{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}}}
|
||||
</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 class="field">
|
||||
<label>密码</label>
|
||||
<input type="text" name="db_password">
|
||||
<input type="password" name="db_password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="three fields">
|
||||
|
@ -75,7 +75,13 @@
|
|||
<div class="three fields">
|
||||
<div class="field">
|
||||
<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 class="three fields">
|
||||
|
@ -164,6 +170,15 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
confirmAdminPassword: {
|
||||
identifier : 'confirm_admin_password',
|
||||
rules: [
|
||||
{
|
||||
type : 'match[admin_password]',
|
||||
prompt : '两次输入密码不匹配'
|
||||
}
|
||||
]
|
||||
},
|
||||
adminEmail: {
|
||||
identifier : 'admin_email',
|
||||
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="2" {{{if eq .Params.Status 1}}}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>
|
||||
</div>
|
||||
<div class="field">
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="four fields" id="hostField">
|
||||
<div class="three fields" id="hostField">
|
||||
<div class="field">
|
||||
<label>主机</label>
|
||||
<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>
|
||||
{{{end}}}
|
||||
</select> <a class="ui blue button" href="/host/create">添加主机</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
|
@ -112,7 +113,7 @@
|
|||
重试时间间隔 重试次数 * 分钟, 按1分钟、2分钟、3分钟.....的间隔进行重试<br>
|
||||
取值范围1-10, 默认0,不重试
|
||||
</div>
|
||||
<input type="text" name="timeout" value="{{{.Task.Timeout}}}">
|
||||
<input type="text" name="retry_times" value="{{{.Task.RetryTimes}}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="three fields">
|
||||
|
|
Loading…
Reference in New Issue