mirror of
https://github.com/ouqiang/gocron.git
synced 2024-04-21 12:31:58 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32caa5a4b7 | ||
|
|
738b168689 | ||
|
|
e1fa3cf645 | ||
|
|
3ed64f9f22 | ||
|
|
94c9963e81 | ||
|
|
4c940c2088 | ||
|
|
50638e6dfa | ||
|
|
f081d89fb4 | ||
|
|
fd39434446 | ||
|
|
72349248ae | ||
|
|
02e525ab83 | ||
|
|
0950fc69f9 | ||
|
|
750fb49a37 | ||
|
|
68d569a6de | ||
|
|
3fd5b2ee55 | ||
|
|
e9b475a230 |
@@ -0,0 +1 @@
|
||||
.git
|
||||
+8
-5
@@ -23,16 +23,19 @@ _testmain.go
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
.DS_Store
|
||||
.idea
|
||||
log/*
|
||||
data/*
|
||||
conf/*
|
||||
log
|
||||
data
|
||||
conf
|
||||
profile/*
|
||||
public/resource/javascript/vue.js
|
||||
gocron
|
||||
/gocron
|
||||
gocron.exe
|
||||
gocron-node
|
||||
/gocron-node
|
||||
gocron-node.exe
|
||||
/bin
|
||||
/packages
|
||||
|
||||
node_modules
|
||||
package.json
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.7.x
|
||||
- 1.9.x
|
||||
script: go test `go list ./... | grep -v vendor`
|
||||
|
||||
notifications:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
FROM alpine:latest
|
||||
|
||||
COPY gocron /usr/local/gocron
|
||||
|
||||
WORKDIR /usr/local/gocron
|
||||
|
||||
EXPOSE 5920
|
||||
|
||||
ENTRYPOINT ["/usr/local/gocron/gocron", "web"]
|
||||
@@ -25,9 +25,9 @@
|
||||
* 任务执行结果通知, 支持邮件、Slack
|
||||
|
||||
### 截图
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
### 支持平台
|
||||
> Windows、Linux、Mac OS
|
||||
@@ -56,12 +56,12 @@
|
||||
4. 浏览器访问 http://localhost:5920
|
||||
|
||||
### 源码安装
|
||||
1. `go`语言版本1.7+
|
||||
1. `go`语言版本1.9+
|
||||
2. `go get -d github.com/ouqiang/gocron`
|
||||
3. 编译
|
||||
* 调度器 `go build -tags gocron -o gocron`
|
||||
* 任务节点 `go build -tags node -o gocron-node`
|
||||
4. 启动、访问方式同上
|
||||
3. 编译 `make`
|
||||
4. 启动
|
||||
* gocron `./bin/gocron web`
|
||||
* gocron-node `./bin/gocron-node`
|
||||
|
||||
### 命令
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
* 定时任务调度 [Cron](https://github.com/robfig/cron)
|
||||
* ORM [Xorm](https://github.com/go-xorm/xorm)
|
||||
* UI框架 [Semantic UI](https://semantic-ui.com/)
|
||||
* 依赖管理(所有依赖包放入vendor目录) [Govendor](https://github.com/kardianos/govendor)
|
||||
* 依赖管理 [Govendor](https://github.com/kardianos/govendor)
|
||||
* RPC框架 [gRPC](https://github.com/grpc/grpc)
|
||||
|
||||
## 反馈
|
||||
@@ -104,6 +104,13 @@
|
||||
|
||||
## ChangeLog
|
||||
|
||||
v1.4
|
||||
--------
|
||||
* HTTP任务支持POST请求
|
||||
* 后台手动停止运行中的shell任务
|
||||
* 任务执行失败重试间隔时间支持用户自定义
|
||||
* 修复API接口调用报403错误
|
||||
|
||||
v1.3
|
||||
--------
|
||||
* 支持多用户登录
|
||||
@@ -118,8 +125,6 @@ v1.2.2
|
||||
* 调度器与任务节点支持HTTPS双向认证
|
||||
* 修复任务列表页总记录数显示错误
|
||||
|
||||
|
||||
|
||||
v1.1
|
||||
--------
|
||||
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# set -x -u
|
||||
# 构建应用, 生成压缩包 gocron.zip或gocron.tar.gz
|
||||
# ./build.sh -p windows -a amd64
|
||||
# 参数含义
|
||||
# -p 指定平台(windows|linux|darwin)
|
||||
# -a 指定体系架构(amd64|386), 默认amd64
|
||||
|
||||
|
||||
TEMP_DIR=`date +%s`-temp-`echo $RANDOM`
|
||||
|
||||
# 目标平台 windows,linux,darwin
|
||||
OS=''
|
||||
# 目标平台架构
|
||||
ARCH=''
|
||||
# 应用名称
|
||||
APP_NAME='gocron'
|
||||
# 版本号
|
||||
VERSION=''
|
||||
# 可执行文件名
|
||||
EXEC_NAME=''
|
||||
# 压缩包名称
|
||||
COMPRESS_FILE=''
|
||||
|
||||
|
||||
# -p 平台 -a 架构
|
||||
while getopts "p:a:v:" OPT;
|
||||
do
|
||||
case $OPT in
|
||||
p) OS=$OPTARG
|
||||
;;
|
||||
a) ARCH=$OPTARG
|
||||
;;
|
||||
v) VERSION=$OPTARG
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z $OS ]];then
|
||||
echo "平台不能为空"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $OS != 'windows' && $OS != 'linux' && $OS != 'darwin' ]];then
|
||||
echo '平台错误,支持的平台 windows linux darmin(osx)'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z $ARCH ]];then
|
||||
ARCH='amd64'
|
||||
fi
|
||||
|
||||
if [[ $ARCH != '386' && $ARCH != 'amd64' ]];then
|
||||
echo 'arch错误,仅支持 386 amd64'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z $VERSION ]];then
|
||||
echo '版本号不能为空'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo '开始编译调度器'
|
||||
if [[ $OS = 'windows' ]];then
|
||||
GOOS=$OS GOARCH=$ARCH go build -tags gocron -ldflags '-w'
|
||||
else
|
||||
GOOS=$OS GOARCH=$ARCH go build -tags gocron -ldflags '-w'
|
||||
fi
|
||||
|
||||
if [[ $? != 0 ]];then
|
||||
exit 1
|
||||
fi
|
||||
echo '编译完成'
|
||||
|
||||
if [[ $OS = 'windows' ]];then
|
||||
EXEC_NAME=${APP_NAME}.exe
|
||||
COMPRESS_FILE=${APP_NAME}-v${VERSION}-${OS}-${ARCH}.zip
|
||||
else
|
||||
EXEC_NAME=${APP_NAME}
|
||||
COMPRESS_FILE=${APP_NAME}-v${VERSION}-${OS}-${ARCH}.tar.gz
|
||||
fi
|
||||
|
||||
mkdir -p $TEMP_DIR/$APP_NAME
|
||||
if [[ $? != 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 需要打包的文件
|
||||
PACKAGE_FILENAME=(conf log public data templates ${EXEC_NAME})
|
||||
|
||||
echo '复制文件到临时目录'
|
||||
# 复制文件到临时目录
|
||||
for i in ${PACKAGE_FILENAME[*]}
|
||||
do
|
||||
cp -r $i $TEMP_DIR/$APP_NAME
|
||||
done
|
||||
|
||||
# 删除运行时产生的文件
|
||||
rm -rf $TEMP_DIR/$APP_NAME/conf/*
|
||||
rm -rf $TEMP_DIR/$APP_NAME/log/*
|
||||
rm -rf $TEMP_DIR/$APP_NAME/data/sessions/*
|
||||
rm -rf $TEMP_DIR/$APP_NAME/data/ssh/password/*
|
||||
rm -rf $TEMP_DIR/$APP_NAME/data/ssh/private_key/*
|
||||
|
||||
echo '压缩文件'
|
||||
# 压缩文件
|
||||
cd $TEMP_DIR
|
||||
if [[ $OS = 'windows' ]];then
|
||||
zip -rq $COMPRESS_FILE *
|
||||
else
|
||||
tar czf $COMPRESS_FILE *
|
||||
fi
|
||||
mv $COMPRESS_FILE ../
|
||||
cd ../
|
||||
|
||||
rm $EXEC_NAME
|
||||
rm -rf $TEMP_DIR
|
||||
|
||||
echo '打包完成'
|
||||
echo '生成压缩文件--' $COMPRESS_FILE
|
||||
@@ -1,92 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# set -x -u
|
||||
# 任务节点打包, 生成压缩包 gocron-node.zip或gocron-node.tar.gz
|
||||
# ./build-node.sh -p windows -a amd64
|
||||
# 参数含义
|
||||
# -p 指定平台(windows|linux|darwin)
|
||||
# -a 指定体系架构(amd64|386), 默认amd64
|
||||
|
||||
|
||||
# 目标平台 windows,linux,darwin
|
||||
OS=''
|
||||
# 目标平台架构
|
||||
ARCH=''
|
||||
# 应用名称
|
||||
APP_NAME='gocron-node'
|
||||
# 版本号
|
||||
VERSION=''
|
||||
# 可执行文件名
|
||||
EXEC_NAME=''
|
||||
# 压缩包名称
|
||||
COMPRESS_FILE=''
|
||||
|
||||
|
||||
# -p 平台 -a 架构
|
||||
while getopts "p:a:v:" OPT;
|
||||
do
|
||||
case $OPT in
|
||||
p) OS=$OPTARG
|
||||
;;
|
||||
a) ARCH=$OPTARG
|
||||
;;
|
||||
v) VERSION=$OPTARG
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z $OS ]];then
|
||||
echo "平台不能为空"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $OS != 'windows' && $OS != 'linux' && $OS != 'darwin' ]];then
|
||||
echo '平台错误,支持的平台 windows linux darmin(osx)'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z $ARCH ]];then
|
||||
ARCH='amd64'
|
||||
fi
|
||||
|
||||
if [[ $ARCH != '386' && $ARCH != 'amd64' ]];then
|
||||
echo 'arch错误,仅支持 386 amd64'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z $VERSION ]];then
|
||||
echo '版本号不能为空'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $OS = 'windows' ]];then
|
||||
EXEC_NAME=${APP_NAME}.exe
|
||||
COMPRESS_FILE=${APP_NAME}-v${VERSION}-${OS}-${ARCH}.zip
|
||||
else
|
||||
EXEC_NAME=${APP_NAME}
|
||||
COMPRESS_FILE=${APP_NAME}-v${VERSION}-${OS}-${ARCH}.tar.gz
|
||||
fi
|
||||
|
||||
echo '开始编译任务节点'
|
||||
if [[ $OS = 'windows' ]];then
|
||||
GOOS=$OS GOARCH=$ARCH go build -tags node -ldflags '-w' -o $EXEC_NAME
|
||||
else
|
||||
GOOS=$OS GOARCH=$ARCH go build -tags node -ldflags '-w' -o $EXEC_NAME
|
||||
fi
|
||||
|
||||
if [[ $? != 0 ]];then
|
||||
exit 1
|
||||
fi
|
||||
echo '编译完成'
|
||||
|
||||
if [[ $OS = 'windows' ]];then
|
||||
zip -rq $COMPRESS_FILE $EXEC_NAME
|
||||
else
|
||||
tar czf $COMPRESS_FILE $EXEC_NAME
|
||||
fi
|
||||
|
||||
|
||||
rm $EXEC_NAME
|
||||
|
||||
echo '打包完成'
|
||||
echo '生成压缩文件--' $COMPRESS_FILE
|
||||
@@ -1,45 +1,63 @@
|
||||
package cmd
|
||||
// main gocron
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/ouqiang/gocron/models"
|
||||
"github.com/ouqiang/gocron/modules/app"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/ouqiang/gocron/modules/rpc/grpcpool"
|
||||
"github.com/ouqiang/gocron/modules/setting"
|
||||
"github.com/ouqiang/gocron/routers"
|
||||
"github.com/ouqiang/gocron/service"
|
||||
"github.com/urfave/cli"
|
||||
"gopkg.in/macaron.v1"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var AppVersion = "1.4"
|
||||
|
||||
// web服务器默认端口
|
||||
const DefaultPort = 5920
|
||||
|
||||
var CmdWeb = cli.Command{
|
||||
Name: "web",
|
||||
Usage: "run web server",
|
||||
Action: runWeb,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "host",
|
||||
Value: "0.0.0.0",
|
||||
Usage: "bind host",
|
||||
func main() {
|
||||
cliApp := cli.NewApp()
|
||||
cliApp.Name = "gocron"
|
||||
cliApp.Usage = "gocron service"
|
||||
cliApp.Version = AppVersion
|
||||
cliApp.Commands = getCommands()
|
||||
cliApp.Flags = append(cliApp.Flags, []cli.Flag{}...)
|
||||
cliApp.Run(os.Args)
|
||||
}
|
||||
|
||||
// getCommands
|
||||
func getCommands() []cli.Command {
|
||||
command := cli.Command{
|
||||
Name: "web",
|
||||
Usage: "run web server",
|
||||
Action: runWeb,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "host",
|
||||
Value: "0.0.0.0",
|
||||
Usage: "bind host",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "port,p",
|
||||
Value: DefaultPort,
|
||||
Usage: "bind port",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "env,e",
|
||||
Value: "prod",
|
||||
Usage: "runtime environment, dev|test|prod",
|
||||
},
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "port,p",
|
||||
Value: DefaultPort,
|
||||
Usage: "bind port",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "env,e",
|
||||
Value: "prod",
|
||||
Usage: "runtime environment, dev|test|prod",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return []cli.Command{command}
|
||||
}
|
||||
|
||||
func runWeb(ctx *cli.Context) {
|
||||
@@ -80,13 +98,12 @@ func initModule() {
|
||||
upgradeIfNeed()
|
||||
|
||||
// 初始化定时任务
|
||||
serviceTask := new(service.Task)
|
||||
serviceTask.Initialize()
|
||||
service.ServiceTask.Initialize()
|
||||
}
|
||||
|
||||
// 解析端口
|
||||
func parsePort(ctx *cli.Context) int {
|
||||
var port int = DefaultPort
|
||||
port := DefaultPort
|
||||
if ctx.IsSet("port") {
|
||||
port = ctx.Int("port")
|
||||
}
|
||||
@@ -106,7 +123,7 @@ func parseHost(ctx *cli.Context) string {
|
||||
}
|
||||
|
||||
func setEnvironment(ctx *cli.Context) {
|
||||
var env string = "prod"
|
||||
env := "prod"
|
||||
if ctx.IsSet("env") {
|
||||
env = ctx.String("env")
|
||||
}
|
||||
@@ -149,26 +166,9 @@ func shutdown() {
|
||||
return
|
||||
}
|
||||
logger.Info("应用准备退出")
|
||||
serviceTask := new(service.Task)
|
||||
// 停止所有任务调度
|
||||
logger.Info("停止定时任务调度")
|
||||
serviceTask.StopAll()
|
||||
|
||||
taskNumInRunning := service.TaskNum.Num()
|
||||
logger.Infof("正在运行的任务有%d个", taskNumInRunning)
|
||||
if taskNumInRunning > 0 {
|
||||
logger.Info("等待所有任务执行完成后退出")
|
||||
}
|
||||
for {
|
||||
if taskNumInRunning <= 0 {
|
||||
break
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
taskNumInRunning = service.TaskNum.Num()
|
||||
}
|
||||
|
||||
// 释放gRPC连接池
|
||||
grpcpool.Pool.ReleaseAll()
|
||||
service.ServiceTask.WaitAndExit()
|
||||
}
|
||||
|
||||
// 判断应用是否需要升级, 当存在版本号文件且版本小于app.VersionId时升级
|
||||
@@ -1,20 +1,20 @@
|
||||
// +build node
|
||||
// 任务节点
|
||||
// main gocron-node
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/ouqiang/gocron/modules/rpc/auth"
|
||||
"github.com/ouqiang/gocron/modules/rpc/server"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/ouqiang/gocron/modules/rpc/auth"
|
||||
"github.com/ouqiang/gocron/modules/rpc/server"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
)
|
||||
|
||||
const AppVersion = "1.3"
|
||||
var AppVersion = "1.4"
|
||||
|
||||
func main() {
|
||||
var serverAddr string
|
||||
@@ -1,25 +0,0 @@
|
||||
// +build gocron
|
||||
// 调度中心
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli"
|
||||
"os"
|
||||
|
||||
"github.com/ouqiang/gocron/cmd"
|
||||
)
|
||||
|
||||
const AppVersion = "1.3"
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "gocron"
|
||||
app.Usage = "gocron service"
|
||||
app.Version = AppVersion
|
||||
app.Commands = []cli.Command{
|
||||
cmd.CmdWeb,
|
||||
}
|
||||
app.Flags = append(app.Flags, []cli.Flag{}...)
|
||||
app.Run(os.Args)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
all: build
|
||||
|
||||
build: gocron node
|
||||
|
||||
gocron:
|
||||
|
||||
go build -o bin/gocron ./cmd/gocron
|
||||
|
||||
|
||||
node:
|
||||
|
||||
go build -o bin/gocron-node ./cmd/node
|
||||
|
||||
clean:
|
||||
|
||||
rm bin/gocron
|
||||
rm bin/gocron-node
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
LDFLAGS = -w -s -X main.AppVersion=${VERSION}
|
||||
|
||||
|
||||
all: build
|
||||
|
||||
|
||||
build: gocron node
|
||||
|
||||
|
||||
gocron:
|
||||
|
||||
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./gocron_darwin_amd64/gocron ./cmd/gocron
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./gocron_linux_amd64/gocron ./cmd/gocron
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./gocron_windows_amd64/gocron.exe ./cmd/gocron
|
||||
|
||||
node:
|
||||
|
||||
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./gocron-node_darwin_amd64/gocron-node ./cmd/node
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./gocron-node_linux_amd64/gocron-node ./cmd/node
|
||||
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./gocron-node_windows_amd64/gocron-node.exe ./cmd/node
|
||||
|
||||
|
||||
clean:
|
||||
|
||||
rm -rf gocron_darwin_amd64
|
||||
rm -rf gocron_linux_amd64
|
||||
rm -rf gocron_windows_amd64
|
||||
|
||||
rm -rf gocron-node_darwin_amd64
|
||||
rm -rf gocron-node_linux_amd64
|
||||
rm -rf gocron-node_windows_amd64
|
||||
|
||||
+27
-4
@@ -3,9 +3,10 @@ package models
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Migration struct{}
|
||||
@@ -53,11 +54,12 @@ func (migration *Migration) Upgrade(oldVersionId int) {
|
||||
return
|
||||
}
|
||||
|
||||
versionIds := []int{110, 122, 130}
|
||||
versionIds := []int{110, 122, 130, 140}
|
||||
upgradeFuncs := []func(*xorm.Session) error{
|
||||
migration.upgradeFor110,
|
||||
migration.upgradeFor122,
|
||||
migration.upgradeFor130,
|
||||
migration.upgradeFor140,
|
||||
}
|
||||
|
||||
startIndex := -1
|
||||
@@ -157,7 +159,7 @@ func (migration *Migration) upgradeFor122(session *xorm.Session) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 升级到1.2.3版本
|
||||
// 升级到v1.3版本
|
||||
func (migration *Migration) upgradeFor130(session *xorm.Session) error {
|
||||
logger.Info("开始升级到v1.3")
|
||||
|
||||
@@ -168,4 +170,25 @@ func (migration *Migration) upgradeFor130(session *xorm.Session) error {
|
||||
logger.Info("已升级到v1.3\n")
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 升级到v1.4版本
|
||||
func (migration *Migration) upgradeFor140(session *xorm.Session) error {
|
||||
logger.Info("开始升级到v1.4")
|
||||
|
||||
tableName := TablePrefix + "task"
|
||||
// task表增加字段
|
||||
// retry_interval 重试间隔时间(秒)
|
||||
// http_method http请求方法
|
||||
sql := fmt.Sprintf(
|
||||
"ALTER TABLE %s ADD COLUMN retry_interval SMALLINT NOT NULL DEFAULT 0,ADD COLUMN http_method TINYINT NOT NULL DEFAULT 1", tableName)
|
||||
_, err := session.Exec(sql)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("已升级到v1.4\n")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
+5
-4
@@ -2,6 +2,9 @@ package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/go-xorm/core"
|
||||
"github.com/go-xorm/xorm"
|
||||
@@ -9,14 +12,12 @@ import (
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/ouqiang/gocron/modules/setting"
|
||||
"gopkg.in/macaron.v1"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Status int8
|
||||
type CommonMap map[string]interface{}
|
||||
|
||||
var TablePrefix string = ""
|
||||
var TablePrefix = ""
|
||||
var Db *xorm.Engine
|
||||
|
||||
const (
|
||||
@@ -99,7 +100,7 @@ func CreateTmpDb(setting *setting.Setting) (*xorm.Engine, error) {
|
||||
// 获取数据库引擎DSN mysql,sqlite
|
||||
func getDbEngineDSN(setting *setting.Setting) string {
|
||||
engine := strings.ToLower(setting.Db.Engine)
|
||||
var dsn string = ""
|
||||
dsn := ""
|
||||
switch engine {
|
||||
case "mysql":
|
||||
dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s",
|
||||
|
||||
+22
-8
@@ -2,9 +2,10 @@ package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/go-xorm/xorm"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
type TaskProtocol int8
|
||||
@@ -28,21 +29,30 @@ const (
|
||||
TaskDependencyStatusWeak TaskDependencyStatus = 2 // 弱依赖
|
||||
)
|
||||
|
||||
type TaskHTTPMethod int8
|
||||
|
||||
const (
|
||||
TaskHTTPMethodGet TaskHTTPMethod = 1
|
||||
TaskHttpMethodPost TaskHTTPMethod = 2
|
||||
)
|
||||
|
||||
// 任务
|
||||
type Task struct {
|
||||
Id int `xorm:"int pk autoincr"`
|
||||
Name string `xorm:"varchar(32) notnull"` // 任务名称
|
||||
Level TaskLevel `xorm:"smallint notnull index default 1"` // 任务等级 1: 主任务 2: 依赖任务
|
||||
Level TaskLevel `xorm:"tinyint notnull index default 1"` // 任务等级 1: 主任务 2: 依赖任务
|
||||
DependencyTaskId string `xorm:"varchar(64) notnull default ''"` // 依赖任务ID,多个ID逗号分隔
|
||||
DependencyStatus TaskDependencyStatus `xorm:"smallint notnull default 1"` // 依赖关系 1:强依赖 主任务执行成功, 依赖任务才会被执行 2:弱依赖
|
||||
DependencyStatus TaskDependencyStatus `xorm:"tinyint notnull default 1"` // 依赖关系 1:强依赖 主任务执行成功, 依赖任务才会被执行 2:弱依赖
|
||||
Spec string `xorm:"varchar(64) notnull"` // crontab
|
||||
Protocol TaskProtocol `xorm:"tinyint notnull index"` // 协议 1:http 2:系统命令
|
||||
Command string `xorm:"varchar(256) notnull"` // URL地址或shell命令
|
||||
HttpMethod TaskHTTPMethod `xorm:"tinyint notnull default 1"` // http请求方法
|
||||
Timeout int `xorm:"mediumint notnull default 0"` // 任务执行超时时间(单位秒),0不限制
|
||||
Multi int8 `xorm:"tinyint notnull default 1"` // 是否允许多实例运行
|
||||
RetryTimes int8 `xorm:"tinyint notnull default 0"` // 重试次数
|
||||
NotifyStatus int8 `xorm:"smallint notnull default 1"` // 任务执行结束是否通知 0: 不通知 1: 失败通知 2: 执行结束通知
|
||||
NotifyType int8 `xorm:"smallint notnull default 0"` // 通知类型 1: 邮件 2: slack
|
||||
RetryInterval int16 `xorm:"smallint notnull default 0"` // 重试间隔时间
|
||||
NotifyStatus int8 `xorm:"tinyint notnull default 1"` // 任务执行结束是否通知 0: 不通知 1: 失败通知 2: 执行结束通知
|
||||
NotifyType int8 `xorm:"tinyint notnull default 0"` // 通知类型 1: 邮件 2: slack
|
||||
NotifyReceiverId string `xorm:"varchar(256) notnull default '' "` // 通知接受者ID, setting表主键ID,多个ID逗号分隔
|
||||
Tag string `xorm:"varchar(32) notnull default ''"`
|
||||
Remark string `xorm:"varchar(100) notnull default ''"` // 备注
|
||||
@@ -83,7 +93,9 @@ func (task *Task) CreateTestTask() {
|
||||
|
||||
func (task *Task) UpdateBean(id int) (int64, error) {
|
||||
return Db.ID(id).
|
||||
Cols("name,spec,protocol,command,timeout,multi,retry_times,remark,notify_status,notify_type,notify_receiver_id, dependency_task_id, dependency_status, tag").
|
||||
Cols(`name,spec,protocol,command,timeout,multi,
|
||||
retry_times,retry_interval,remark,notify_status,
|
||||
notify_type,notify_receiver_id, dependency_task_id, dependency_status, tag,http_method`).
|
||||
Update(task)
|
||||
}
|
||||
|
||||
@@ -108,9 +120,11 @@ func (task *Task) Enable(id int) (int64, error) {
|
||||
}
|
||||
|
||||
// 获取所有激活任务
|
||||
func (task *Task) ActiveList() ([]Task, error) {
|
||||
func (task *Task) ActiveList(page, pageSize int) ([]Task, error) {
|
||||
params := CommonMap{"Page": page, "PageSize": pageSize}
|
||||
task.parsePageAndPageSize(params)
|
||||
list := make([]Task, 0)
|
||||
err := Db.Where("status = ? AND level = ?", Enabled, TaskLevelParent).
|
||||
err := Db.Where("status = ? AND level = ?", Enabled, TaskLevelParent).Limit(task.PageSize, task.pageLimitOffset()).
|
||||
Find(&list)
|
||||
|
||||
if err != nil {
|
||||
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/go-xorm/xorm"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
type TaskType int8
|
||||
|
||||
+3
-2
@@ -1,8 +1,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"time"
|
||||
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
)
|
||||
|
||||
const PasswordSaltLength = 6
|
||||
@@ -78,7 +79,7 @@ func (user *User) Match(username, password string) bool {
|
||||
}
|
||||
|
||||
// 获取用户详情
|
||||
func (user *User) Find(id int) (error) {
|
||||
func (user *User) Find(id int) error {
|
||||
_, err := Db.Id(id).Get(user)
|
||||
|
||||
return err
|
||||
|
||||
+13
-7
@@ -3,12 +3,14 @@ package app
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/ouqiang/gocron/modules/setting"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/ouqiang/gocron/modules/setting"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -35,7 +37,7 @@ func InitEnv(versionString string) {
|
||||
DataDir = AppDir + "/data"
|
||||
AppConfig = ConfDir + "/app.ini"
|
||||
VersionFile = ConfDir + "/.version"
|
||||
checkDirExists(ConfDir, LogDir, DataDir)
|
||||
createDirIfNotExists(ConfDir, LogDir, DataDir)
|
||||
Installed = IsInstalled()
|
||||
VersionId = ToNumberVersion(versionString)
|
||||
}
|
||||
@@ -107,10 +109,14 @@ func ToNumberVersion(versionString string) int {
|
||||
}
|
||||
|
||||
// 检测目录是否存在
|
||||
func checkDirExists(path ...string) {
|
||||
func createDirIfNotExists(path ...string) {
|
||||
for _, value := range path {
|
||||
if !utils.FileExist(value) {
|
||||
logger.Fatal(value + "目录不存在或无权限访问")
|
||||
if utils.FileExist(value) {
|
||||
continue
|
||||
}
|
||||
err := os.Mkdir(value, 0755)
|
||||
if err != nil {
|
||||
logger.Fatal(fmt.Sprintf("创建目录失败:%s", err.Error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +73,7 @@ func request(req *http.Request, timeout int) ResponseWrapper {
|
||||
}
|
||||
|
||||
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")
|
||||
req.Header.Set("User-Agent", "golang/gocron")
|
||||
}
|
||||
|
||||
func createRequestError(err error) ResponseWrapper {
|
||||
|
||||
@@ -2,10 +2,11 @@ package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cihub/seelog"
|
||||
"gopkg.in/macaron.v1"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/cihub/seelog"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
// 日志库
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-gomail/gomail"
|
||||
"github.com/ouqiang/gocron/models"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// @author qiang.ou<qingqianludao@gmail.com>
|
||||
|
||||
@@ -2,8 +2,9 @@ package notify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"time"
|
||||
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
)
|
||||
|
||||
type Message map[string]interface{}
|
||||
@@ -12,7 +13,7 @@ type Notifiable interface {
|
||||
Send(msg Message)
|
||||
}
|
||||
|
||||
var queue chan Message = make(chan Message, 100)
|
||||
var queue = make(chan Message, 100)
|
||||
|
||||
func init() {
|
||||
go run()
|
||||
|
||||
@@ -4,13 +4,14 @@ package notify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ouqiang/gocron/models"
|
||||
"github.com/ouqiang/gocron/modules/httpclient"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Slack struct{}
|
||||
|
||||
@@ -5,8 +5,9 @@ import (
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"io/ioutil"
|
||||
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
type Certificate struct {
|
||||
|
||||
@@ -3,19 +3,38 @@ package client
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/ouqiang/gocron/modules/rpc/grpcpool"
|
||||
pb "github.com/ouqiang/gocron/modules/rpc/proto"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
taskMap sync.Map
|
||||
)
|
||||
|
||||
var (
|
||||
errUnavailable = errors.New("无法连接远程服务器")
|
||||
)
|
||||
|
||||
func generateTaskUniqueKey(ip string, port int, id int64) string {
|
||||
return fmt.Sprintf("%s:%d:%d", ip, port, id)
|
||||
}
|
||||
|
||||
func Stop(ip string, port int, id int64) {
|
||||
key := generateTaskUniqueKey(ip, port, id)
|
||||
cancel, ok := taskMap.Load(key)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
cancel.(context.CancelFunc)()
|
||||
}
|
||||
|
||||
func Exec(ip string, port int, taskReq *pb.TaskRequest) (string, error) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
@@ -39,7 +58,9 @@ func Exec(ip string, port int, taskReq *pb.TaskRequest) (string, error) {
|
||||
}
|
||||
timeout := time.Duration(taskReq.Timeout) * time.Second
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
taskUniqueKey := generateTaskUniqueKey(ip, port, taskReq.Id)
|
||||
taskMap.Store(taskUniqueKey, cancel)
|
||||
defer taskMap.Delete(taskUniqueKey)
|
||||
resp, err := c.Run(ctx, taskReq)
|
||||
if err != nil {
|
||||
return parseGRPCError(err, conn, &isConnClosed)
|
||||
@@ -60,6 +81,8 @@ func parseGRPCError(err error, conn *grpc.ClientConn, connClosed *bool) (string,
|
||||
return "", errUnavailable
|
||||
case codes.DeadlineExceeded:
|
||||
return "", errors.New("执行超时, 强制结束")
|
||||
case codes.Canceled:
|
||||
return "", errors.New("手动停止")
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ package grpcpool
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ouqiang/gocron/modules/app"
|
||||
"github.com/ouqiang/gocron/modules/rpc/auth"
|
||||
"github.com/silenceper/pool"
|
||||
"google.golang.org/grpc"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -36,6 +36,7 @@ const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
type TaskRequest struct {
|
||||
Command string `protobuf:"bytes,2,opt,name=command" json:"command,omitempty"`
|
||||
Timeout int32 `protobuf:"varint,3,opt,name=timeout" json:"timeout,omitempty"`
|
||||
Id int64 `protobuf:"varint,4,opt,name=id" json:"id,omitempty"`
|
||||
}
|
||||
|
||||
func (m *TaskRequest) Reset() { *m = TaskRequest{} }
|
||||
@@ -57,6 +58,13 @@ func (m *TaskRequest) GetTimeout() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *TaskRequest) GetId() int64 {
|
||||
if m != nil {
|
||||
return m.Id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type TaskResponse struct {
|
||||
Output string `protobuf:"bytes,1,opt,name=output" json:"output,omitempty"`
|
||||
Error string `protobuf:"bytes,2,opt,name=error" json:"error,omitempty"`
|
||||
@@ -161,16 +169,17 @@ var _Task_serviceDesc = grpc.ServiceDesc{
|
||||
func init() { proto.RegisterFile("task.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 170 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8f, 0xb1, 0x0e, 0x82, 0x40,
|
||||
0x0c, 0x86, 0x45, 0x04, 0x63, 0x75, 0xd0, 0xc6, 0x98, 0x8b, 0x13, 0x61, 0x62, 0x30, 0x0c, 0xea,
|
||||
0xe8, 0xe2, 0x2b, 0x5c, 0x7c, 0x01, 0xc4, 0x1b, 0x0c, 0x81, 0x9e, 0xbd, 0xde, 0xfb, 0x1b, 0x38,
|
||||
0x48, 0x18, 0xbf, 0x36, 0xfd, 0xfa, 0xff, 0x00, 0x52, 0xb9, 0xa6, 0xb4, 0x4c, 0x42, 0x18, 0xb3,
|
||||
0xad, 0xf3, 0x27, 0x6c, 0x5f, 0x95, 0x6b, 0xb4, 0xf9, 0x79, 0xe3, 0x04, 0x15, 0xac, 0x6b, 0x6a,
|
||||
0xdb, 0xaa, 0xfb, 0xa8, 0x65, 0x16, 0x15, 0x1b, 0x3d, 0x61, 0xbf, 0x91, 0x6f, 0x6b, 0xc8, 0x8b,
|
||||
0x8a, 0xb3, 0xa8, 0x48, 0xf4, 0x84, 0xf9, 0x03, 0x76, 0x41, 0xe1, 0x2c, 0x75, 0xce, 0xe0, 0x09,
|
||||
0x52, 0xf2, 0x62, 0xbd, 0xa8, 0x68, 0x50, 0x8c, 0x84, 0x47, 0x48, 0x0c, 0x33, 0xf1, 0x68, 0x0e,
|
||||
0x70, 0xbd, 0xc3, 0xaa, 0xbf, 0xc6, 0x0b, 0xc4, 0xda, 0x77, 0xb8, 0x2f, 0xd9, 0xd6, 0xe5, 0x2c,
|
||||
0xd2, 0xf9, 0x30, 0x9b, 0x84, 0x0f, 0xf9, 0xe2, 0x9d, 0x0e, 0x15, 0x6e, 0xff, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xef, 0x3c, 0x71, 0x6b, 0xd0, 0x00, 0x00, 0x00,
|
||||
// 184 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8f, 0xcf, 0x8a, 0x83, 0x30,
|
||||
0x10, 0xc6, 0x37, 0x46, 0x5d, 0x76, 0x76, 0x59, 0xda, 0xa1, 0x94, 0xd0, 0x93, 0x78, 0xf2, 0x50,
|
||||
0x3c, 0xb4, 0x3d, 0xf6, 0x25, 0x1a, 0xfa, 0x02, 0x56, 0x73, 0x10, 0xd1, 0x49, 0x93, 0xc9, 0xfb,
|
||||
0x17, 0xff, 0x81, 0xc7, 0xdf, 0x0c, 0xf3, 0xfd, 0xe6, 0x03, 0xe0, 0xca, 0x77, 0xa5, 0x75, 0xc4,
|
||||
0x84, 0xd2, 0xd9, 0x3a, 0x7f, 0xc0, 0xef, 0xb3, 0xf2, 0x9d, 0x36, 0xef, 0x60, 0x3c, 0xa3, 0x82,
|
||||
0xef, 0x9a, 0xfa, 0xbe, 0x1a, 0x1a, 0x15, 0x65, 0xa2, 0xf8, 0xd1, 0x2b, 0x8e, 0x1b, 0x6e, 0x7b,
|
||||
0x43, 0x81, 0x95, 0xcc, 0x44, 0x91, 0xe8, 0x15, 0xf1, 0x1f, 0xa2, 0xb6, 0x51, 0x71, 0x26, 0x0a,
|
||||
0xa9, 0xa3, 0xb6, 0xc9, 0xef, 0xf0, 0x37, 0x47, 0x7a, 0x4b, 0x83, 0x37, 0x78, 0x84, 0x94, 0x02,
|
||||
0xdb, 0xc0, 0x4a, 0x4c, 0x91, 0x0b, 0xe1, 0x01, 0x12, 0xe3, 0x1c, 0xb9, 0xc5, 0x34, 0xc3, 0xe5,
|
||||
0x06, 0xf1, 0x78, 0x8d, 0x67, 0x90, 0x3a, 0x0c, 0xb8, 0x2b, 0x9d, 0xad, 0xcb, 0xcd, 0x8b, 0xa7,
|
||||
0xfd, 0x66, 0x32, 0x1b, 0xf2, 0xaf, 0x57, 0x3a, 0x55, 0xba, 0x7e, 0x02, 0x00, 0x00, 0xff, 0xff,
|
||||
0xd7, 0x7f, 0x8a, 0x9d, 0xe0, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ service Task {
|
||||
message TaskRequest {
|
||||
string command = 2; // 命令
|
||||
int32 timeout = 3; // 任务执行超时时间
|
||||
int64 id = 4; // 执行任务唯一ID
|
||||
}
|
||||
|
||||
message TaskResponse {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/ouqiang/gocron/modules/rpc/auth"
|
||||
pb "github.com/ouqiang/gocron/modules/rpc/proto"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
@@ -8,7 +10,6 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"net"
|
||||
)
|
||||
|
||||
type Server struct{}
|
||||
@@ -32,12 +33,6 @@ func (s Server) Run(ctx context.Context, req *pb.TaskRequest) (*pb.TaskResponse,
|
||||
}
|
||||
|
||||
func Start(addr string, enableTLS bool, certificate auth.Certificate) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
grpclog.Println("panic", err)
|
||||
}
|
||||
}()
|
||||
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
grpclog.Fatal(err)
|
||||
|
||||
@@ -2,6 +2,7 @@ package setting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"gopkg.in/ini.v1"
|
||||
@@ -32,6 +33,8 @@ type Setting struct {
|
||||
CAFile string
|
||||
CertFile string
|
||||
KeyFile string
|
||||
|
||||
ConcurrencyQueue int
|
||||
}
|
||||
|
||||
// 读取配置
|
||||
@@ -60,6 +63,7 @@ func Read(filename string) (*Setting, error) {
|
||||
s.ApiKey = section.Key("api.key").MustString("")
|
||||
s.ApiSecret = section.Key("api.secret").MustString("")
|
||||
s.ApiSignEnable = section.Key("api.sign.enable").MustBool(true)
|
||||
s.ConcurrencyQueue = section.Key("concurrency.queue").MustInt(500)
|
||||
|
||||
s.EnableTLS = section.Key("enable_tls").MustBool(false)
|
||||
s.CAFile = section.Key("ca_file").MustString("")
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HostAuthType int8 // 认证方式
|
||||
|
||||
const (
|
||||
HostPassword = 1 // 密码认证
|
||||
HostPublicKey = 2 // 公钥认证
|
||||
)
|
||||
|
||||
const SSHConnectTimeout = 10
|
||||
|
||||
type SSHConfig struct {
|
||||
AuthType HostAuthType
|
||||
User string
|
||||
Password string
|
||||
PrivateKey string
|
||||
Host string
|
||||
Port int
|
||||
ExecTimeout int // 执行超时时间
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Output string
|
||||
Err error
|
||||
}
|
||||
|
||||
func parseSSHConfig(sshConfig SSHConfig) (config *ssh.ClientConfig, err error) {
|
||||
timeout := time.Duration(SSHConnectTimeout) * time.Second
|
||||
// 密码认证
|
||||
if sshConfig.AuthType == HostPassword {
|
||||
config = &ssh.ClientConfig{
|
||||
User: sshConfig.User,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password(sshConfig.Password),
|
||||
},
|
||||
Timeout: timeout,
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
signer, err := ssh.ParsePrivateKey([]byte(sshConfig.PrivateKey))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 公钥认证
|
||||
config = &ssh.ClientConfig{
|
||||
User: sshConfig.User,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
Timeout: timeout,
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 执行shell命令
|
||||
func Exec(sshConfig SSHConfig, cmd string) (output string, err error) {
|
||||
client, err := getClient(sshConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
session, err := client.NewSession()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// 后台运行
|
||||
if sshConfig.ExecTimeout < 0 {
|
||||
go session.CombinedOutput(cmd)
|
||||
time.Sleep(10 * time.Second)
|
||||
return "", nil
|
||||
}
|
||||
// 不限制超时
|
||||
if sshConfig.ExecTimeout == 0 {
|
||||
outputByte, execErr := session.CombinedOutput(cmd)
|
||||
output = string(outputByte)
|
||||
err = execErr
|
||||
return
|
||||
}
|
||||
|
||||
var resultChan chan Result = make(chan Result)
|
||||
var timeoutChan chan bool = make(chan bool)
|
||||
go func() {
|
||||
output, err := session.CombinedOutput(cmd)
|
||||
resultChan <- Result{string(output), err}
|
||||
}()
|
||||
// todo 等待超时后,如何停止远程正在执行的任务, 使用timeout命令,但不具有通用性
|
||||
go triggerTimeout(timeoutChan, sshConfig.ExecTimeout)
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
output = result.Output
|
||||
err = result.Err
|
||||
case <-timeoutChan:
|
||||
output = ""
|
||||
err = errors.New("timeout")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getClient(sshConfig SSHConfig) (*ssh.Client, error) {
|
||||
config, err := parseSSHConfig(sshConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", sshConfig.Host, sshConfig.Port)
|
||||
|
||||
return ssh.Dial("tcp", addr, config)
|
||||
}
|
||||
|
||||
func triggerTimeout(ch chan bool, timeout int) {
|
||||
// 最长执行时间不能超过24小时
|
||||
if timeout <= 0 || timeout > 86400 {
|
||||
timeout = 86400
|
||||
}
|
||||
time.Sleep(time.Duration(timeout) * time.Second)
|
||||
close(ch)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
)
|
||||
|
||||
|
||||
+2
-13
@@ -3,13 +3,12 @@ package utils
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/Tang-RoseChild/mahonia"
|
||||
"math/rand"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Tang-RoseChild/mahonia"
|
||||
)
|
||||
|
||||
// 生成长度为length的随机字符串
|
||||
@@ -41,11 +40,6 @@ func RandNumber(max int) int {
|
||||
return r.Intn(max)
|
||||
}
|
||||
|
||||
// 判断当前系统是否是windows
|
||||
func IsWindows() bool {
|
||||
return runtime.GOOS == "windows"
|
||||
}
|
||||
|
||||
// GBK编码转换为UTF8
|
||||
func GBK2UTF8(s string) (string, bool) {
|
||||
dec := mahonia.NewDecoder("gbk")
|
||||
@@ -100,8 +94,3 @@ func FileExist(file string) bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 格式化环境变量
|
||||
func FormatUnixEnv(key, value string) string {
|
||||
return fmt.Sprintf("export %s=%s; ", key, value)
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"golang.org/x/net/context"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
@@ -20,7 +21,7 @@ func ExecShell(ctx context.Context, command string) (string, error) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
}
|
||||
var resultChan chan Result = make(chan Result)
|
||||
resultChan := make(chan Result)
|
||||
go func() {
|
||||
output, err := cmd.CombinedOutput()
|
||||
resultChan <- Result{string(output), err}
|
||||
|
||||
@@ -4,10 +4,11 @@ package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"golang.org/x/net/context"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#set -x -u
|
||||
# 构建应用, 生成压缩包 gocron.zip或gocron.tar.gz
|
||||
# ./package.sh -v 1.4
|
||||
|
||||
VERSION=''
|
||||
GOCRON_APP_NAME='gocron'
|
||||
GOCRON_NODE_APP_NAME='gocron-node'
|
||||
PACKAGE_DIR='./packages'
|
||||
|
||||
|
||||
# 用法
|
||||
usage() {
|
||||
echo 'usage: ./package.sh -v version'
|
||||
}
|
||||
|
||||
# 初始化
|
||||
init() {
|
||||
rm -rf ${PACKAGE_DIR}
|
||||
mkdir -p ${PACKAGE_DIR}
|
||||
}
|
||||
|
||||
# 构建应用
|
||||
build() {
|
||||
make -f makefile.cross-compiles VERSION=${VERSION}
|
||||
|
||||
if [[ $? -ne 0 ]];then
|
||||
echo 'make error'
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 清理
|
||||
clean() {
|
||||
make -f makefile.cross-compiles clean
|
||||
}
|
||||
|
||||
# 打包gocron
|
||||
package_gocron() {
|
||||
local OS=$1
|
||||
local GOCRON_COMPRESS_FILE=''
|
||||
local PLATFORM_NAME=${GOCRON_APP_NAME}_${OS}_amd64
|
||||
|
||||
if [[ ! -d ${PACKAGE_DIR}/${PLATFORM_NAME} ]];then
|
||||
mkdir ${PACKAGE_DIR}/${PLATFORM_NAME}
|
||||
fi
|
||||
|
||||
for file in public templates LICENSE README.md Dockerfile-release; do
|
||||
cp -r ${file} ${PLATFORM_NAME}
|
||||
done
|
||||
|
||||
if [[ ${OS} = 'windows' ]];then
|
||||
GOCRON_COMPRESS_FILE=${GOCRON_APP_NAME}-v${VERSION}-${OS}-amd64.zip
|
||||
zip -rq ${PACKAGE_DIR}/${PLATFORM_NAME}/${GOCRON_COMPRESS_FILE} ${PLATFORM_NAME}
|
||||
else
|
||||
GOCRON_COMPRESS_FILE=${GOCRON_APP_NAME}-v${VERSION}-${OS}-amd64.tar.gz
|
||||
tar czf ${PACKAGE_DIR}/${PLATFORM_NAME}/${GOCRON_COMPRESS_FILE} ${PLATFORM_NAME}
|
||||
fi
|
||||
}
|
||||
|
||||
# 打包gocron-node
|
||||
package_gocron_node() {
|
||||
local OS=$1
|
||||
local GOCRON_NODE_COMPRESS_FILE=''
|
||||
local PLATFORM_NAME=${GOCRON_NODE_APP_NAME}_${OS}_amd64
|
||||
|
||||
if [[ ! -d ${PACKAGE_DIR}/${PLATFORM_NAME} ]];then
|
||||
mkdir ${PACKAGE_DIR}/${PLATFORM_NAME}
|
||||
fi
|
||||
|
||||
if [[ ${OS} = 'windows' ]];then
|
||||
GOCRON_NODE_COMPRESS_FILE=${GOCRON_NODE_APP_NAME}-v${VERSION}-${OS}-amd64.zip
|
||||
zip -rq ${PACKAGE_DIR}/${PLATFORM_NAME}/${GOCRON_NODE_COMPRESS_FILE} ${PLATFORM_NAME}
|
||||
else
|
||||
GOCRON_NODE_COMPRESS_FILE=${GOCRON_NODE_APP_NAME}-v${VERSION}-${OS}-amd64.tar.gz
|
||||
tar czf ${PACKAGE_DIR}/${PLATFORM_NAME}/${GOCRON_NODE_COMPRESS_FILE} ${PLATFORM_NAME}
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
package_multi() {
|
||||
for os in darwin linux windows; do
|
||||
package_gocron ${os}
|
||||
package_gocron_node ${os}
|
||||
done
|
||||
}
|
||||
|
||||
while getopts "v:" OPT; do
|
||||
case ${OPT} in
|
||||
v) VERSION=${OPTARG}
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z ${VERSION} ]]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
run() {
|
||||
init
|
||||
build
|
||||
package_multi
|
||||
clean
|
||||
}
|
||||
|
||||
|
||||
run
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@ package host
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/paginater"
|
||||
"github.com/go-macaron/binding"
|
||||
"github.com/ouqiang/gocron/models"
|
||||
@@ -13,9 +17,6 @@ import (
|
||||
"github.com/ouqiang/gocron/routers/base"
|
||||
"github.com/ouqiang/gocron/service"
|
||||
"gopkg.in/macaron.v1"
|
||||
"html/template"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Index 主机列表
|
||||
@@ -127,8 +128,7 @@ func Store(ctx *macaron.Context, form HostForm) string {
|
||||
if err != nil {
|
||||
return json.CommonFailure("刷新任务主机信息失败", err)
|
||||
}
|
||||
serviceTask := new(service.Task)
|
||||
serviceTask.BatchAdd(tasks)
|
||||
service.ServiceTask.BatchAdd(tasks)
|
||||
}
|
||||
|
||||
return json.Success("保存成功", nil)
|
||||
@@ -190,7 +190,7 @@ func Ping(ctx *macaron.Context) string {
|
||||
|
||||
// 解析查询参数
|
||||
func parseQueryParams(ctx *macaron.Context) models.CommonMap {
|
||||
var params models.CommonMap = models.CommonMap{}
|
||||
var params = models.CommonMap{}
|
||||
params["Id"] = ctx.QueryInt("id")
|
||||
params["Name"] = ctx.QueryTrim("name")
|
||||
base.ParsePageAndPageSize(ctx, params)
|
||||
|
||||
@@ -2,6 +2,8 @@ package install
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-macaron/binding"
|
||||
"github.com/ouqiang/gocron/models"
|
||||
"github.com/ouqiang/gocron/modules/app"
|
||||
@@ -9,7 +11,6 @@ import (
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"github.com/ouqiang/gocron/service"
|
||||
"gopkg.in/macaron.v1"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// 系统安装
|
||||
@@ -97,8 +98,7 @@ func Store(ctx *macaron.Context, form InstallForm) string {
|
||||
|
||||
app.Installed = true
|
||||
// 初始化定时任务
|
||||
serviceTask := new(service.Task)
|
||||
serviceTask.Initialize()
|
||||
service.ServiceTask.Initialize()
|
||||
|
||||
return json.Success("安装成功", nil)
|
||||
}
|
||||
@@ -121,6 +121,7 @@ func writeConfig(form InstallForm) error {
|
||||
"api.key", "",
|
||||
"api.secret", "",
|
||||
"enable_tls", "false",
|
||||
"concurrency.queue", "500",
|
||||
"ca_file", "",
|
||||
"cert_file", "",
|
||||
"key_file", "",
|
||||
|
||||
@@ -2,12 +2,13 @@ package loginlog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
||||
"github.com/Unknwon/paginater"
|
||||
"github.com/ouqiang/gocron/models"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/ouqiang/gocron/routers/base"
|
||||
"gopkg.in/macaron.v1"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
func Index(ctx *macaron.Context) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package manage
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ouqiang/gocron/models"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
|
||||
+18
-20
@@ -1,6 +1,12 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-macaron/binding"
|
||||
"github.com/go-macaron/cache"
|
||||
"github.com/go-macaron/captcha"
|
||||
@@ -18,10 +24,6 @@ import (
|
||||
"github.com/ouqiang/gocron/routers/tasklog"
|
||||
"github.com/ouqiang/gocron/routers/user"
|
||||
"gopkg.in/macaron.v1"
|
||||
"html/template"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 静态文件目录
|
||||
@@ -65,6 +67,7 @@ func Register(m *macaron.Macaron) {
|
||||
m.Get("", task.Index)
|
||||
m.Get("/log", tasklog.Index)
|
||||
m.Post("/log/clear", tasklog.Clear)
|
||||
m.Post("/log/stop", tasklog.Stop)
|
||||
m.Post("/remove/:id", task.Remove)
|
||||
m.Post("/enable/:id", task.Enable)
|
||||
m.Post("/disable/:id", task.Disable)
|
||||
@@ -112,7 +115,7 @@ func Register(m *macaron.Macaron) {
|
||||
m.NotFound(func(ctx *macaron.Context) {
|
||||
if isGetRequest(ctx) && !isAjaxRequest(ctx) {
|
||||
ctx.Data["Title"] = "404 - NOT FOUND"
|
||||
ctx.HTML(404, "error/404")
|
||||
ctx.HTML(http.StatusNotFound, "error/404")
|
||||
} else {
|
||||
json := utils.JsonResponse{}
|
||||
ctx.Resp.Write([]byte(json.Failure(utils.NotFound, "您访问的地址不存在")))
|
||||
@@ -123,7 +126,7 @@ func Register(m *macaron.Macaron) {
|
||||
logger.Debug("500错误")
|
||||
if isGetRequest(ctx) && !isAjaxRequest(ctx) {
|
||||
ctx.Data["Title"] = "500 - INTERNAL SERVER ERROR"
|
||||
ctx.HTML(500, "error/500")
|
||||
ctx.HTML(http.StatusInternalServerError, "error/500")
|
||||
} else {
|
||||
json := utils.JsonResponse{}
|
||||
ctx.Resp.Write([]byte(json.Failure(utils.ServerError, "网站暂时无法访问,请稍后再试")))
|
||||
@@ -140,16 +143,8 @@ func RegisterMiddleware(m *macaron.Macaron) {
|
||||
}
|
||||
m.Use(macaron.Static(StaticDir))
|
||||
m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
Directory: "templates",
|
||||
Extensions: []string{".html"},
|
||||
// 模板语法分隔符,默认为 ["{{", "}}"]
|
||||
Delims: macaron.Delims{"{{{", "}}}"},
|
||||
// 追加的 Content-Type 头信息,默认为 "UTF-8"
|
||||
Charset: "UTF-8",
|
||||
// 渲染具有缩进格式的 JSON,默认为不缩进
|
||||
IndentJSON: true,
|
||||
// 渲染具有缩进格式的 XML,默认为不缩进
|
||||
IndentXML: true,
|
||||
Delims: macaron.Delims{"{%", "%}"},
|
||||
Funcs: []template.FuncMap{map[string]interface{}{
|
||||
"HostFormat": func(index int) bool {
|
||||
return (index+1)%3 == 0
|
||||
@@ -202,7 +197,7 @@ func ipAuth(ctx *macaron.Context) {
|
||||
allowIps := strings.Split(allowIpsStr, ",")
|
||||
if !utils.InStringSlice(allowIps, clientIp) {
|
||||
logger.Warnf("非法IP访问-%s", clientIp)
|
||||
ctx.Status(403)
|
||||
ctx.Status(http.StatusForbidden)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,10 +221,15 @@ func userAuth(ctx *macaron.Context, sess session.Store) {
|
||||
}
|
||||
|
||||
// URL权限验证
|
||||
func urlAuth(ctx *macaron.Context, sess session.Store) {
|
||||
func urlAuth(ctx *macaron.Context, sess session.Store) {
|
||||
if user.IsAdmin(sess) {
|
||||
return
|
||||
}
|
||||
uri := strings.TrimSpace(ctx.Req.URL.Path)
|
||||
uri = strings.TrimRight(uri, "/")
|
||||
if strings.HasPrefix(uri, "/api") {
|
||||
return
|
||||
}
|
||||
// 普通用户允许访问的URL地址
|
||||
allowPaths := []string{
|
||||
"",
|
||||
@@ -240,15 +240,13 @@ func urlAuth(ctx *macaron.Context, sess session.Store) {
|
||||
"/user/logout",
|
||||
"/user/editMyPassword",
|
||||
}
|
||||
uri := strings.TrimSpace(ctx.Req.URL.Path)
|
||||
uri = strings.TrimRight(uri, "/")
|
||||
for _, path := range allowPaths {
|
||||
if path == uri {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Status(403)
|
||||
ctx.Status(http.StatusUnauthorized)
|
||||
|
||||
}
|
||||
|
||||
|
||||
+23
-16
@@ -2,6 +2,10 @@ package task
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/paginater"
|
||||
"github.com/go-macaron/binding"
|
||||
"github.com/jakecoffman/cron"
|
||||
@@ -11,9 +15,6 @@ import (
|
||||
"github.com/ouqiang/gocron/routers/base"
|
||||
"github.com/ouqiang/gocron/service"
|
||||
"gopkg.in/macaron.v1"
|
||||
"html/template"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TaskForm struct {
|
||||
@@ -23,11 +24,13 @@ type TaskForm struct {
|
||||
DependencyTaskId string
|
||||
Name string `binding:"Required;MaxSize(32)"`
|
||||
Spec string
|
||||
Protocol models.TaskProtocol `binding:"In(1,2)"`
|
||||
Command string `binding:"Required;MaxSize(256)"`
|
||||
Timeout int `binding:"Range(0,86400)"`
|
||||
Multi int8 `binding:"In(1,2)"`
|
||||
Protocol models.TaskProtocol `binding:"In(1,2)"`
|
||||
Command string `binding:"Required;MaxSize(256)"`
|
||||
HttpMethod models.TaskHTTPMethod `binding:"In(1,2)"`
|
||||
Timeout int `binding:"Range(0,86400)"`
|
||||
Multi int8 `binding:"In(1,2)"`
|
||||
RetryTimes int8
|
||||
RetryInterval int16
|
||||
HostId string
|
||||
Tag string
|
||||
Remark string
|
||||
@@ -110,11 +113,11 @@ func Edit(ctx *macaron.Context) {
|
||||
ctx.HTML(200, "task/task_form")
|
||||
}
|
||||
|
||||
// 保存任务
|
||||
// 保存任务 todo 拆分为多个方法 快变成意大利面条式代码了
|
||||
func Store(ctx *macaron.Context, form TaskForm) string {
|
||||
json := utils.JsonResponse{}
|
||||
taskModel := models.Task{}
|
||||
var id int = form.Id
|
||||
var id = form.Id
|
||||
nameExists, err := taskModel.NameExist(form.Name, form.Id)
|
||||
if err != nil {
|
||||
return json.CommonFailure(utils.FailureContent, err)
|
||||
@@ -129,12 +132,13 @@ func Store(ctx *macaron.Context, form TaskForm) string {
|
||||
|
||||
taskModel.Name = form.Name
|
||||
taskModel.Protocol = form.Protocol
|
||||
taskModel.Command = form.Command
|
||||
taskModel.Command = strings.TrimSpace(form.Command)
|
||||
taskModel.Timeout = form.Timeout
|
||||
taskModel.Tag = form.Tag
|
||||
taskModel.Remark = form.Remark
|
||||
taskModel.Multi = form.Multi
|
||||
taskModel.RetryTimes = form.RetryTimes
|
||||
taskModel.RetryInterval = form.RetryInterval
|
||||
if taskModel.Multi != 1 {
|
||||
taskModel.Multi = 0
|
||||
}
|
||||
@@ -148,6 +152,7 @@ func Store(ctx *macaron.Context, form TaskForm) string {
|
||||
if taskModel.NotifyStatus > 0 && taskModel.NotifyReceiverId == "" {
|
||||
return json.CommonFailure("至少选择一个通知接收者")
|
||||
}
|
||||
taskModel.HttpMethod = form.HttpMethod
|
||||
if taskModel.Protocol == models.TaskHTTP {
|
||||
command := strings.ToLower(taskModel.Command)
|
||||
if !strings.HasPrefix(command, "http://") && !strings.HasPrefix(command, "https://") {
|
||||
@@ -162,6 +167,10 @@ func Store(ctx *macaron.Context, form TaskForm) string {
|
||||
return json.CommonFailure("任务重试次数取值0-10")
|
||||
}
|
||||
|
||||
if taskModel.RetryInterval > 3600 || taskModel.RetryInterval < 0 {
|
||||
return json.CommonFailure("任务重试间隔时间取值0-3600")
|
||||
}
|
||||
|
||||
if taskModel.DependencyStatus != models.TaskDependencyStatusStrong &&
|
||||
taskModel.DependencyStatus != models.TaskDependencyStatusWeak {
|
||||
return json.CommonFailure("请选择依赖关系")
|
||||
@@ -229,7 +238,7 @@ func Remove(ctx *macaron.Context) string {
|
||||
taskHostModel := new(models.TaskHost)
|
||||
taskHostModel.Remove(id)
|
||||
|
||||
service.Cron.RemoveJob(strconv.Itoa(id))
|
||||
service.ServiceTask.Remove(id)
|
||||
|
||||
return json.Success(utils.SuccessContent, nil)
|
||||
}
|
||||
@@ -255,8 +264,7 @@ func Run(ctx *macaron.Context) string {
|
||||
}
|
||||
|
||||
task.Spec = "手动运行"
|
||||
serviceTask := new(service.Task)
|
||||
serviceTask.Run(task)
|
||||
service.ServiceTask.Run(task)
|
||||
|
||||
return json.Success("任务已开始运行, 请到任务日志中查看结果", nil)
|
||||
}
|
||||
@@ -276,7 +284,7 @@ func changeStatus(ctx *macaron.Context, status models.Status) string {
|
||||
if status == models.Enabled {
|
||||
addTaskToTimer(id)
|
||||
} else {
|
||||
service.Cron.RemoveJob(strconv.Itoa(id))
|
||||
service.ServiceTask.Remove(id)
|
||||
}
|
||||
|
||||
return json.Success(utils.SuccessContent, nil)
|
||||
@@ -291,8 +299,7 @@ func addTaskToTimer(id int) {
|
||||
return
|
||||
}
|
||||
|
||||
taskService := service.Task{}
|
||||
taskService.Add(task)
|
||||
service.ServiceTask.RemoveAndAdd(task)
|
||||
}
|
||||
|
||||
// 解析查询参数
|
||||
|
||||
@@ -4,13 +4,15 @@ package tasklog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
||||
"github.com/Unknwon/paginater"
|
||||
"github.com/ouqiang/gocron/models"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"github.com/ouqiang/gocron/routers/base"
|
||||
"github.com/ouqiang/gocron/service"
|
||||
"gopkg.in/macaron.v1"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
func Index(ctx *macaron.Context) {
|
||||
@@ -48,6 +50,30 @@ func Clear(ctx *macaron.Context) string {
|
||||
return json.Success(utils.SuccessContent, nil)
|
||||
}
|
||||
|
||||
// 停止运行中的任务
|
||||
func Stop(ctx *macaron.Context) string {
|
||||
id := ctx.QueryInt64("id")
|
||||
taskId := ctx.QueryInt("task_id")
|
||||
taskModel := new(models.Task)
|
||||
task, err := taskModel.Detail(taskId)
|
||||
json := utils.JsonResponse{}
|
||||
if err != nil {
|
||||
return json.CommonFailure("获取任务信息失败#"+err.Error(), err)
|
||||
}
|
||||
if task.Protocol != models.TaskRPC {
|
||||
return json.CommonFailure("仅支持SHELL任务手动停止")
|
||||
}
|
||||
if len(task.Hosts) == 0 {
|
||||
return json.CommonFailure("任务节点列表为空")
|
||||
}
|
||||
for _, host := range task.Hosts {
|
||||
service.ServiceTask.Stop(host.Name, host.Port, id)
|
||||
|
||||
}
|
||||
|
||||
return json.Success("已执行停止操作, 请等待任务退出", nil)
|
||||
}
|
||||
|
||||
// 删除N个月前的日志
|
||||
func Remove(ctx *macaron.Context) string {
|
||||
month := ctx.ParamsInt(":id")
|
||||
|
||||
+17
-17
@@ -1,6 +1,11 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/paginater"
|
||||
"github.com/go-macaron/captcha"
|
||||
"github.com/go-macaron/session"
|
||||
"github.com/ouqiang/gocron/models"
|
||||
@@ -8,10 +13,6 @@ import (
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"github.com/ouqiang/gocron/routers/base"
|
||||
"gopkg.in/macaron.v1"
|
||||
"github.com/Unknwon/paginater"
|
||||
"html/template"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// @author qiang.ou<qingqianludao@gmail.com>
|
||||
@@ -19,17 +20,17 @@ import (
|
||||
|
||||
// UserForm 用户表单
|
||||
type UserForm struct {
|
||||
Id int
|
||||
Name string `binding:"Required;MaxSize(32)"` // 用户名
|
||||
Password string // 密码
|
||||
ConfirmPassword string // 确认密码
|
||||
Email string `binding:"Required;MaxSize(50)"` // 邮箱
|
||||
IsAdmin int8 // 是否是管理员 1:管理员 0:普通用户
|
||||
Status models.Status
|
||||
Id int
|
||||
Name string `binding:"Required;MaxSize(32)"` // 用户名
|
||||
Password string // 密码
|
||||
ConfirmPassword string // 确认密码
|
||||
Email string `binding:"Required;MaxSize(50)"` // 邮箱
|
||||
IsAdmin int8 // 是否是管理员 1:管理员 0:普通用户
|
||||
Status models.Status
|
||||
}
|
||||
|
||||
// Index 用户列表页
|
||||
func Index(ctx *macaron.Context) {
|
||||
func Index(ctx *macaron.Context) {
|
||||
queryParams := parseQueryParams(ctx)
|
||||
userModel := new(models.User)
|
||||
users, err := userModel.List(queryParams)
|
||||
@@ -59,7 +60,7 @@ func parseQueryParams(ctx *macaron.Context) models.CommonMap {
|
||||
}
|
||||
|
||||
// Create 新增用户页
|
||||
func Create(ctx *macaron.Context) {
|
||||
func Create(ctx *macaron.Context) {
|
||||
userModel := new(models.User)
|
||||
userModel.Status = models.Enabled
|
||||
userModel.IsAdmin = 0
|
||||
@@ -129,9 +130,9 @@ func Store(ctx *macaron.Context, form UserForm) string {
|
||||
}
|
||||
} else {
|
||||
_, err = userModel.Update(form.Id, models.CommonMap{
|
||||
"name": form.Name,
|
||||
"email": form.Email,
|
||||
"status": form.Status,
|
||||
"name": form.Name,
|
||||
"email": form.Email,
|
||||
"status": form.Status,
|
||||
"is_admin": form.IsAdmin,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -139,7 +140,6 @@ func Store(ctx *macaron.Context, form UserForm) string {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return json.Success("保存成功", nil)
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 295 KiB After Width: | Height: | Size: 295 KiB |
+148
-90
@@ -3,81 +3,96 @@ package service
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jakecoffman/cron"
|
||||
"github.com/ouqiang/gocron/models"
|
||||
"github.com/ouqiang/gocron/modules/app"
|
||||
"github.com/ouqiang/gocron/modules/httpclient"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/ouqiang/gocron/modules/notify"
|
||||
rpcClient "github.com/ouqiang/gocron/modules/rpc/client"
|
||||
pb "github.com/ouqiang/gocron/modules/rpc/proto"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 定时任务调度管理器
|
||||
var Cron *cron.Cron
|
||||
var (
|
||||
ServiceTask Task
|
||||
)
|
||||
|
||||
// 同一任务是否有实例处于运行中
|
||||
var runInstance Instance
|
||||
var (
|
||||
// 定时任务调度管理器
|
||||
serviceCron *cron.Cron
|
||||
|
||||
// 任务计数-正在运行中的任务
|
||||
var TaskNum TaskCount
|
||||
// 同一任务是否有实例处于运行中
|
||||
runInstance Instance
|
||||
|
||||
// 任务计数-正在运行的任务
|
||||
taskCount TaskCount
|
||||
|
||||
// 并发队列, 限制同时运行的任务数量
|
||||
concurrencyQueue ConcurrencyQueue
|
||||
)
|
||||
|
||||
// 并发队列
|
||||
type ConcurrencyQueue struct {
|
||||
queue chan struct{}
|
||||
}
|
||||
|
||||
func (cq *ConcurrencyQueue) Add() {
|
||||
cq.queue <- struct{}{}
|
||||
}
|
||||
|
||||
func (cq *ConcurrencyQueue) Done() {
|
||||
<-cq.queue
|
||||
}
|
||||
|
||||
// 任务计数
|
||||
type TaskCount struct {
|
||||
num int
|
||||
sync.RWMutex
|
||||
wg sync.WaitGroup
|
||||
exit chan struct{}
|
||||
}
|
||||
|
||||
func (c *TaskCount) Add() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.num += 1
|
||||
func (tc *TaskCount) Add() {
|
||||
tc.wg.Add(1)
|
||||
}
|
||||
|
||||
func (c *TaskCount) Done() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.num -= 1
|
||||
func (tc *TaskCount) Done() {
|
||||
tc.wg.Done()
|
||||
}
|
||||
|
||||
func (c *TaskCount) Num() int {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
func (tc *TaskCount) Exit() {
|
||||
tc.wg.Done()
|
||||
<-tc.exit
|
||||
}
|
||||
|
||||
return c.num
|
||||
func (tc *TaskCount) Wait() {
|
||||
tc.Add()
|
||||
tc.wg.Wait()
|
||||
close(tc.exit)
|
||||
}
|
||||
|
||||
// 任务ID作为Key
|
||||
type Instance struct {
|
||||
Status map[int]bool
|
||||
sync.RWMutex
|
||||
m sync.Map
|
||||
}
|
||||
|
||||
// 是否有任务处于运行中
|
||||
func (i *Instance) has(key int) bool {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
running, ok := i.Status[key]
|
||||
if ok && running {
|
||||
return true
|
||||
}
|
||||
_, ok := i.m.Load(key)
|
||||
|
||||
return false
|
||||
return ok
|
||||
}
|
||||
|
||||
func (i *Instance) add(key int) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
i.Status[key] = true
|
||||
i.m.Store(key, struct{}{})
|
||||
}
|
||||
|
||||
func (i *Instance) done(key int) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
delete(i.Status, key)
|
||||
i.m.Delete(key)
|
||||
}
|
||||
|
||||
type Task struct{}
|
||||
@@ -89,34 +104,51 @@ type TaskResult struct {
|
||||
}
|
||||
|
||||
// 初始化任务, 从数据库取出所有任务, 添加到定时任务并运行
|
||||
func (task *Task) Initialize() {
|
||||
Cron = cron.New()
|
||||
Cron.Start()
|
||||
runInstance = Instance{make(map[int]bool), sync.RWMutex{}}
|
||||
TaskNum = TaskCount{0, sync.RWMutex{}}
|
||||
func (task Task) Initialize() {
|
||||
serviceCron = cron.New()
|
||||
serviceCron.Start()
|
||||
concurrencyQueue = ConcurrencyQueue{queue: make(chan struct{}, app.Setting.ConcurrencyQueue)}
|
||||
taskCount = TaskCount{sync.WaitGroup{}, make(chan struct{})}
|
||||
go taskCount.Wait()
|
||||
|
||||
logger.Info("开始初始化定时任务")
|
||||
taskModel := new(models.Task)
|
||||
taskList, err := taskModel.ActiveList()
|
||||
if err != nil {
|
||||
logger.Error("定时任务初始化#获取任务列表错误-", err.Error())
|
||||
return
|
||||
taskNum := 0
|
||||
page := 1
|
||||
pageSize := 1000
|
||||
maxPage := 1000
|
||||
for page < maxPage {
|
||||
taskList, err := taskModel.ActiveList(page, pageSize)
|
||||
if err != nil {
|
||||
logger.Fatalf("定时任务初始化#获取任务列表错误-", err.Error())
|
||||
}
|
||||
if len(taskList) == 0 {
|
||||
break
|
||||
}
|
||||
for _, item := range taskList {
|
||||
task.Add(item)
|
||||
taskNum++
|
||||
}
|
||||
page++
|
||||
}
|
||||
if len(taskList) == 0 {
|
||||
logger.Debug("任务列表为空")
|
||||
return
|
||||
}
|
||||
task.BatchAdd(taskList)
|
||||
logger.Infof("定时任务初始化完成, 共%d个定时任务添加到调度器", taskNum)
|
||||
}
|
||||
|
||||
// 批量添加任务
|
||||
func (task *Task) BatchAdd(tasks []models.Task) {
|
||||
func (task Task) BatchAdd(tasks []models.Task) {
|
||||
for _, item := range tasks {
|
||||
task.Add(item)
|
||||
task.RemoveAndAdd(item)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除任务后添加
|
||||
func (task Task) RemoveAndAdd(taskModel models.Task) {
|
||||
task.Remove(taskModel.Id)
|
||||
task.Add(taskModel)
|
||||
}
|
||||
|
||||
// 添加任务
|
||||
func (task *Task) Add(taskModel models.Task) {
|
||||
func (task Task) Add(taskModel models.Task) {
|
||||
if taskModel.Level == models.TaskLevelChild {
|
||||
logger.Errorf("添加任务失败#不允许添加子任务到调度器#任务Id-%d", taskModel.Id)
|
||||
return
|
||||
@@ -128,26 +160,34 @@ func (task *Task) Add(taskModel models.Task) {
|
||||
}
|
||||
|
||||
cronName := strconv.Itoa(taskModel.Id)
|
||||
// Cron任务采用数组存储, 删除任务需遍历数组, 并对数组重新赋值, 任务较多时,有性能问题
|
||||
Cron.RemoveJob(cronName)
|
||||
err := Cron.AddFunc(taskModel.Spec, taskFunc, cronName)
|
||||
err := serviceCron.AddFunc(taskModel.Spec, taskFunc, cronName)
|
||||
if err != nil {
|
||||
logger.Error("添加任务到调度器失败#", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 停止所有任务
|
||||
func (task *Task) StopAll() {
|
||||
Cron.Stop()
|
||||
// 停止运行中的任务
|
||||
func (task Task) Stop(ip string, port int, id int64) {
|
||||
rpcClient.Stop(ip, port, id)
|
||||
}
|
||||
|
||||
func (task Task) Remove(id int) {
|
||||
serviceCron.RemoveJob(strconv.Itoa(id))
|
||||
}
|
||||
|
||||
// 等待所有任务结束后退出
|
||||
func (task Task) WaitAndExit() {
|
||||
serviceCron.Stop()
|
||||
taskCount.Exit()
|
||||
}
|
||||
|
||||
// 直接运行任务
|
||||
func (task *Task) Run(taskModel models.Task) {
|
||||
func (task Task) Run(taskModel models.Task) {
|
||||
go createJob(taskModel)()
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
Run(taskModel models.Task) (string, error)
|
||||
Run(taskModel models.Task, taskUniqueId int64) (string, error)
|
||||
}
|
||||
|
||||
// HTTP任务
|
||||
@@ -156,13 +196,24 @@ type HTTPHandler struct{}
|
||||
// http任务执行时间不超过300秒
|
||||
const HttpExecTimeout = 300
|
||||
|
||||
func (h *HTTPHandler) Run(taskModel models.Task) (result string, err error) {
|
||||
func (h *HTTPHandler) Run(taskModel models.Task, taskUniqueId int64) (result string, err error) {
|
||||
if taskModel.Timeout <= 0 || taskModel.Timeout > HttpExecTimeout {
|
||||
taskModel.Timeout = HttpExecTimeout
|
||||
}
|
||||
resp := httpclient.Get(taskModel.Command, taskModel.Timeout)
|
||||
var resp httpclient.ResponseWrapper
|
||||
if taskModel.HttpMethod == models.TaskHTTPMethodGet {
|
||||
resp = httpclient.Get(taskModel.Command, taskModel.Timeout)
|
||||
} else {
|
||||
urlFields := strings.Split(taskModel.Command, "?")
|
||||
taskModel.Command = urlFields[0]
|
||||
var params string
|
||||
if len(urlFields) >= 2 {
|
||||
params = urlFields[1]
|
||||
}
|
||||
resp = httpclient.PostParams(taskModel.Command, params, taskModel.Timeout)
|
||||
}
|
||||
// 返回状态码非200,均为失败
|
||||
if resp.StatusCode != 200 {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return resp.Body, errors.New(fmt.Sprintf("HTTP状态码非200-->%d", resp.StatusCode))
|
||||
}
|
||||
|
||||
@@ -172,15 +223,16 @@ func (h *HTTPHandler) Run(taskModel models.Task) (result string, err error) {
|
||||
// RPC调用执行任务
|
||||
type RPCHandler struct{}
|
||||
|
||||
func (h *RPCHandler) Run(taskModel models.Task) (result string, err error) {
|
||||
func (h *RPCHandler) Run(taskModel models.Task, taskUniqueId int64) (result string, err error) {
|
||||
taskRequest := new(pb.TaskRequest)
|
||||
taskRequest.Timeout = int32(taskModel.Timeout)
|
||||
taskRequest.Command = taskModel.Command
|
||||
var resultChan chan TaskResult = make(chan TaskResult, len(taskModel.Hosts))
|
||||
taskRequest.Id = taskUniqueId
|
||||
resultChan := make(chan TaskResult, len(taskModel.Hosts))
|
||||
for _, taskHost := range taskModel.Hosts {
|
||||
go func(th models.TaskHostDetail) {
|
||||
output, err := rpcClient.Exec(th.Name, th.Port, taskRequest)
|
||||
var errorMessage string = ""
|
||||
errorMessage := ""
|
||||
if err != nil {
|
||||
errorMessage = err.Error()
|
||||
}
|
||||
@@ -192,7 +244,7 @@ func (h *RPCHandler) Run(taskModel models.Task) (result string, err error) {
|
||||
}
|
||||
|
||||
var aggregationErr error = nil
|
||||
var aggregationResult string = ""
|
||||
aggregationResult := ""
|
||||
for i := 0; i < len(taskModel.Hosts); i++ {
|
||||
taskResult := <-resultChan
|
||||
aggregationResult += taskResult.Result
|
||||
@@ -214,7 +266,7 @@ func createTaskLog(taskModel models.Task, status models.Status) (int64, error) {
|
||||
taskLogModel.Command = taskModel.Command
|
||||
taskLogModel.Timeout = taskModel.Timeout
|
||||
if taskModel.Protocol == models.TaskRPC {
|
||||
var aggregationHost string = ""
|
||||
aggregationHost := ""
|
||||
for _, host := range taskModel.Hosts {
|
||||
aggregationHost += fmt.Sprintf("%s-%s<br>", host.Alias, host.Name)
|
||||
}
|
||||
@@ -231,7 +283,7 @@ func createTaskLog(taskModel models.Task, status models.Status) (int64, error) {
|
||||
func updateTaskLog(taskLogId int64, taskResult TaskResult) (int64, error) {
|
||||
taskLogModel := new(models.TaskLog)
|
||||
var status models.Status
|
||||
var result string = taskResult.Result
|
||||
result := taskResult.Result
|
||||
if taskResult.Err != nil {
|
||||
status = models.Failure
|
||||
} else {
|
||||
@@ -246,19 +298,29 @@ func updateTaskLog(taskLogId int64, taskResult TaskResult) (int64, error) {
|
||||
}
|
||||
|
||||
func createJob(taskModel models.Task) cron.FuncJob {
|
||||
var handler Handler = createHandler(taskModel)
|
||||
handler := createHandler(taskModel)
|
||||
if handler == nil {
|
||||
return nil
|
||||
}
|
||||
taskFunc := func() {
|
||||
TaskNum.Add()
|
||||
defer TaskNum.Done()
|
||||
taskCount.Add()
|
||||
defer taskCount.Done()
|
||||
|
||||
taskLogId := beforeExecJob(taskModel)
|
||||
if taskLogId <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if taskModel.Multi == 0 {
|
||||
runInstance.add(taskModel.Id)
|
||||
defer runInstance.done(taskModel.Id)
|
||||
}
|
||||
|
||||
concurrencyQueue.Add()
|
||||
defer concurrencyQueue.Done()
|
||||
|
||||
logger.Infof("开始执行任务#%s#命令-%s", taskModel.Name, taskModel.Command)
|
||||
taskResult := execJob(handler, taskModel)
|
||||
taskResult := execJob(handler, taskModel, taskLogId)
|
||||
logger.Infof("任务完成#%s#命令-%s", taskModel.Name, taskModel.Command)
|
||||
afterExecJob(taskModel, taskResult, taskLogId)
|
||||
}
|
||||
@@ -284,15 +346,11 @@ func beforeExecJob(taskModel models.Task) (taskLogId int64) {
|
||||
createTaskLog(taskModel, models.Cancel)
|
||||
return
|
||||
}
|
||||
if taskModel.Multi == 0 {
|
||||
runInstance.add(taskModel.Id)
|
||||
}
|
||||
taskLogId, err := createTaskLog(taskModel, models.Running)
|
||||
if err != nil {
|
||||
logger.Error("任务开始执行#写入任务日志失败-", err)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debugf("任务命令-%s", taskModel.Command)
|
||||
|
||||
return taskLogId
|
||||
@@ -340,10 +398,9 @@ func execDependencyTask(taskModel models.Task, taskResult TaskResult) {
|
||||
if len(tasks) == 0 {
|
||||
logger.Errorf("依赖任务列表为空#主任务ID-%d", taskModel.Id)
|
||||
}
|
||||
serviceTask := new(Task)
|
||||
for _, task := range tasks {
|
||||
task.Spec = fmt.Sprintf("依赖任务(主任务ID-%d)", taskModel.Id)
|
||||
serviceTask.Run(task)
|
||||
ServiceTask.Run(task)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,15 +436,12 @@ func SendNotification(taskModel models.Task, taskResult TaskResult) {
|
||||
}
|
||||
|
||||
// 执行具体任务
|
||||
func execJob(handler Handler, taskModel models.Task) TaskResult {
|
||||
func execJob(handler Handler, taskModel models.Task, taskUniqueId int64) TaskResult {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Error("panic#service/task.go:execJob#", err)
|
||||
}
|
||||
}()
|
||||
if taskModel.Multi == 0 {
|
||||
defer runInstance.done(taskModel.Id)
|
||||
}
|
||||
// 默认只运行任务一次
|
||||
var execTimes int8 = 1
|
||||
if taskModel.RetryTimes > 0 {
|
||||
@@ -397,15 +451,19 @@ func execJob(handler Handler, taskModel models.Task) TaskResult {
|
||||
var output string
|
||||
var err error
|
||||
for i < execTimes {
|
||||
output, err = handler.Run(taskModel)
|
||||
output, err = handler.Run(taskModel, taskUniqueId)
|
||||
if err == nil {
|
||||
return TaskResult{Result: output, Err: err, RetryTimes: i}
|
||||
}
|
||||
i++
|
||||
if i < execTimes {
|
||||
logger.Warnf("任务执行失败#任务id-%d#重试第%d次#输出-%s#错误-%s", taskModel.Id, i, output, err.Error())
|
||||
// 重试间隔时间,每次递增1分钟
|
||||
time.Sleep(time.Duration(i) * time.Minute)
|
||||
if taskModel.RetryInterval > 0 {
|
||||
time.Sleep(time.Duration(taskModel.RetryInterval) * time.Second)
|
||||
} else {
|
||||
// 默认重试间隔时间,每次递增1分钟
|
||||
time.Sleep(time.Duration(i) * time.Minute)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="renderer" content="webkit">
|
||||
<title>{{{.Title}}}</title>
|
||||
<title>{%.Title%}</title>
|
||||
<link type="text/css" rel="stylesheet" href="/resource/css/framework.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/resource/css/main.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/resource/sweetalert/sweetalert.css" />
|
||||
@@ -29,29 +29,29 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
{{{if not .DisableNav}}}
|
||||
{%if not .DisableNav%}
|
||||
<!--header begin-->
|
||||
<header>
|
||||
<div class="bigcontainer">
|
||||
<div id="logo">
|
||||
{{{.AppName}}}
|
||||
{%.AppName%}
|
||||
</div>
|
||||
<div class="user">
|
||||
<div class="ui inline labeled icon top right pointing dropdown">
|
||||
{{{if not .LoginUsername}}}
|
||||
{%if not .LoginUsername%}
|
||||
你好
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="menu">
|
||||
<a class="item" href="/user/login"><i class="sign in icon"></i>登录</a>
|
||||
</div>
|
||||
{{{else}}}
|
||||
你好,{{{.LoginUsername}}}
|
||||
{%else%}
|
||||
你好,{%.LoginUsername%}
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="menu">
|
||||
<a href="/user/editMyPassword" class="item"><i class="edit icon"></i>修改密码</a>
|
||||
<a class="item" href="/user/logout"><i class="sign out icon"></i>退出</a>
|
||||
</div>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,13 +60,13 @@
|
||||
<div class="ui teal inverted menu">
|
||||
<div class="bigcontainer">
|
||||
<div class="right menu">
|
||||
<a class="item {{{if or (eq .Controller "task") (eq .Controller "delaytask")}}}active{{{end}}}" href="/task"><i class="tasks icon"></i>任务</a>
|
||||
<a class="item {{{if eq .Controller "host"}}}active{{{end}}}" href="/host"><i class="linux icon"></i>任务节点</a>
|
||||
{{{if .IsAdmin}}}
|
||||
<a class="item {{{if eq .Controller "user"}}}active{{{end}}}" href="/user"><i class="user icon"></i>用户管理</a>
|
||||
<a class="item {{{if eq .Controller "manage"}}}active{{{end}}}" href="/manage/slack/edit"><i class="settings icon"></i>系统管理</a>
|
||||
{{{end}}}
|
||||
<a class="item {%if or (eq .Controller "task") (eq .Controller "delaytask")%}active{%end%}" href="/task"><i class="tasks icon"></i>任务</a>
|
||||
<a class="item {%if eq .Controller "host"%}active{%end%}" href="/host"><i class="linux icon"></i>任务节点</a>
|
||||
{%if .IsAdmin%}
|
||||
<a class="item {%if eq .Controller "user"%}active{%end%}" href="/user"><i class="user icon"></i>用户管理</a>
|
||||
<a class="item {%if eq .Controller "manage"%}active{%end%}" href="/manage/slack/edit"><i class="settings icon"></i>系统管理</a>
|
||||
{%end%}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
@@ -1,41 +1,41 @@
|
||||
{{{if gt .Pagination.Total 0}}}
|
||||
{%if gt .Pagination.Total 0%}
|
||||
|
||||
<div class="ui pagination menu">
|
||||
{{{if not .Pagination.IsFirst}}}
|
||||
<a class="item" href="{{{.URI}}}?{{{ .Params.PageParams}}}&page=1">
|
||||
{%if not .Pagination.IsFirst%}
|
||||
<a class="item" href="{%.URI%}?{% .Params.PageParams%}&page=1">
|
||||
首页
|
||||
</a>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
|
||||
{{{if .Pagination.HasPrevious}}}
|
||||
<a class="icon item" href="{{{.URI}}}?{{{ .Params.PageParams}}}&page={{{.Pagination.Previous}}}">
|
||||
{%if .Pagination.HasPrevious%}
|
||||
<a class="icon item" href="{%.URI%}?{% .Params.PageParams%}&page={%.Pagination.Previous%}">
|
||||
<i class="icon left arrow"></i>
|
||||
</a>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
|
||||
{{{range .Pagination.Pages}}}
|
||||
{{{if eq .Num -1}}}
|
||||
{%range .Pagination.Pages%}
|
||||
{%if eq .Num -1%}
|
||||
<div class="disabled item">
|
||||
...
|
||||
</div>
|
||||
{{{else}}}
|
||||
<a class="icon {{{if .IsCurrent}}}active{{{end}}} item" href="{{{$.URI}}}?{{{ $.Params.PageParams}}}&page={{{.Num}}}">
|
||||
{{{.Num}}}
|
||||
{%else%}
|
||||
<a class="icon {%if .IsCurrent%}active{%end%} item" href="{%$.URI%}?{% $.Params.PageParams%}&page={%.Num%}">
|
||||
{%.Num%}
|
||||
</a>
|
||||
{{{end}}}
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
{%end%}
|
||||
|
||||
{{{if .Pagination.HasNext}}}
|
||||
<a class="icon item" href="{{{.URI}}}?{{{ .Params.PageParams}}}&page={{{.Pagination.Next}}}">
|
||||
{%if .Pagination.HasNext%}
|
||||
<a class="icon item" href="{%.URI%}?{% .Params.PageParams%}&page={%.Pagination.Next%}">
|
||||
<i class="icon right arrow"></i>
|
||||
</a>
|
||||
{{{end}}}
|
||||
{{{if not .Pagination.IsLast}}}
|
||||
<a class="icon item" href="{{{.URI}}}?{{{ .Params.PageParams}}}&page={{{.Pagination.TotalPages}}}">
|
||||
{%end%}
|
||||
{%if not .Pagination.IsLast%}
|
||||
<a class="icon item" href="{%.URI%}?{% .Params.PageParams%}&page={%.Pagination.TotalPages%}">
|
||||
尾页
|
||||
</a>
|
||||
{{{end}}}
|
||||
<div class="item">{{{.Pagination.Total}}}条/{{{.Pagination.TotalPages}}}页</div>
|
||||
{%end%}
|
||||
<div class="item">{%.Pagination.Total%}条/{%.Pagination.TotalPages%}页</div>
|
||||
</div>
|
||||
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
@@ -1,4 +1,4 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
<script>
|
||||
swal({
|
||||
title: "404 - NOT FOUND",
|
||||
@@ -9,4 +9,4 @@
|
||||
location.href = "/"
|
||||
});
|
||||
</script>
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
@@ -1,4 +1,4 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
<script>
|
||||
swal({
|
||||
title: "500 - INTERNAL SERVER ERROR",
|
||||
@@ -8,4 +8,4 @@
|
||||
confirmButtonText: "确定"
|
||||
});
|
||||
</script>
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
@@ -1,3 +1,3 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
@@ -1,7 +1,7 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
|
||||
<div class="ui grid">
|
||||
{{{ template "host/menu" . }}}
|
||||
{% template "host/menu" . %}
|
||||
|
||||
<div class="twelve wide column">
|
||||
<div class="pageHeader">
|
||||
@@ -14,24 +14,24 @@
|
||||
</div>
|
||||
</div>
|
||||
<form class="ui form fluid vertical segment">
|
||||
<input type="hidden" name="id" value="{{{.Host.Id}}}">
|
||||
<input type="hidden" name="id" value="{%.Host.Id%}">
|
||||
<div class="four fields">
|
||||
<div class="field">
|
||||
<label>主机名</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="name" value="{{{.Host.Name}}}" placeholder="127.0.0.1">
|
||||
<input type="text" name="name" value="{%.Host.Name%}" placeholder="127.0.0.1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>端口</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="port" value="{{{.Host.Port}}}" placeholder="5921">
|
||||
<input type="text" name="port" value="{%.Host.Port%}" placeholder="5921">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>节点名称</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="alias" value="{{{.Host.Alias}}}"
|
||||
<input type="text" name="alias" value="{%.Host.Alias%}"
|
||||
placeholder="节点名称如web">
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,7 +40,7 @@
|
||||
<div class="field">
|
||||
<label>备注</label>
|
||||
<div class="ui small input">
|
||||
<textarea rows="7" name="remark" >{{{.Host.Remark}}}</textarea>
|
||||
<textarea rows="7" name="remark" >{%.Host.Remark%}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -113,4 +113,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
+23
-23
@@ -1,10 +1,10 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
|
||||
<div class="ui grid">
|
||||
{{{ template "host/menu" . }}}
|
||||
{% template "host/menu" . %}
|
||||
|
||||
<div class="twelve wide column">
|
||||
{{{if .IsAdmin}}}
|
||||
{%if .IsAdmin%}
|
||||
<div class="pageHeader">
|
||||
<div class="segment">
|
||||
<h3 class="ui dividing header">
|
||||
@@ -17,14 +17,14 @@
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
<form class="ui form">
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<input type="text" placeholder="ID" name="id" value="{{{if gt .Params.Id 0}}}{{{.Params.Id}}}{{{end}}}">
|
||||
<input type="text" placeholder="ID" name="id" value="{%if gt .Params.Id 0%}{%.Params.Id%}{%end%}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<input type="text" placeholder="主机名" name="name" value="{{{.Params.Name}}}">
|
||||
<input type="text" placeholder="主机名" name="name" value="{%.Params.Name%}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<button class="ui linkedin submit button">搜索</button>
|
||||
@@ -43,30 +43,30 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{{range $i, $v := .Hosts}}}
|
||||
{%range $i, $v := .Hosts%}
|
||||
<tr>
|
||||
<td>{{{.Id}}}</td>
|
||||
<td>{{{.Name}}}</td>
|
||||
<td>{{{.Alias}}}</td>
|
||||
<td>{{{.Port}}}</td>
|
||||
<td>{{{.Remark}}}</td>
|
||||
<td>{%.Id%}</td>
|
||||
<td>{%.Name%}</td>
|
||||
<td>{%.Alias%}</td>
|
||||
<td>{%.Port%}</td>
|
||||
<td>{%.Remark%}</td>
|
||||
<td class="operation">
|
||||
{{{if $.IsAdmin}}}
|
||||
<a class="ui purple button" href="/host/edit/{{{.Id}}}">编辑</a>
|
||||
<button class="ui positive button" onclick="util.removeConfirm('/host/remove/{{{.Id}}}')">删除</button><br>
|
||||
{{{end}}}
|
||||
{%if $.IsAdmin%}
|
||||
<a class="ui purple button" href="/host/edit/{%.Id%}">编辑</a>
|
||||
<button class="ui positive button" onclick="util.removeConfirm('/host/remove/{%.Id%}')">删除</button><br>
|
||||
{%end%}
|
||||
<div style="margin-top: 5px;">
|
||||
<a class="ui twitter button" href="/task?host_id={{{.Id}}}">查看任务</a>
|
||||
{{{if $.IsAdmin}}}
|
||||
<button class="ui blue button" @click="ping({{{.Id}}})">连接测试</button>
|
||||
{{{end}}}
|
||||
<a class="ui twitter button" href="/task?host_id={%.Id%}">查看任务</a>
|
||||
{%if $.IsAdmin%}
|
||||
<button class="ui blue button" @click="ping({%.Id%})">连接测试</button>
|
||||
{%end%}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
</tbody>
|
||||
</table>
|
||||
{{{ template "common/pagination" .}}}
|
||||
{% template "common/pagination" .%}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -83,4 +83,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="four wide column">
|
||||
<div class="verticalMenu">
|
||||
<div class="ui vertical pointing menu fluid">
|
||||
<a class="{{{if eq .URI "/host"}}}active teal{{{end}}} item" href="/host">
|
||||
<a class="{%if eq .URI "/host"%}active teal{%end%} item" href="/host">
|
||||
<i class="linux icon"></i> 节点列表
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
|
||||
<div class="ui grid">
|
||||
{{{ template "install/menu" . }}}
|
||||
{% template "install/menu" . %}
|
||||
<div class="ten wide column">
|
||||
<form class="ui form">
|
||||
<div class="ui blue center aligned segment">
|
||||
@@ -186,4 +186,4 @@
|
||||
|
||||
</script>
|
||||
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
@@ -1,7 +1,7 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
|
||||
<div class="ui grid">
|
||||
{{{ template "manage/menu" . }}}
|
||||
{% template "manage/menu" . %}
|
||||
|
||||
<div class="twelve wide column">
|
||||
<div class="pageHeader">
|
||||
@@ -22,18 +22,18 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{{range $i, $v := .LoginLogs}}}
|
||||
{%range $i, $v := .LoginLogs%}
|
||||
<tr>
|
||||
<td>{{{.Username}}}</td>
|
||||
<td>{{{.Ip}}}</td>
|
||||
<td>{{{.Created}}}</td>
|
||||
<td>{%.Username%}</td>
|
||||
<td>{%.Ip%}</td>
|
||||
<td>{%.Created%}</td>
|
||||
</tr>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
</tbody>
|
||||
</table>
|
||||
{{{ template "common/pagination" .}}}
|
||||
{% template "common/pagination" .%}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
+13
-13
@@ -1,12 +1,12 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
<div class="ui grid">
|
||||
{{{template "manage/menu" .}}}
|
||||
{%template "manage/menu" .%}
|
||||
<div class="twelve wide column">
|
||||
<div class="pageHeader">
|
||||
<div class="segment">
|
||||
<h3 class="ui dividing header">
|
||||
<div class="content">
|
||||
{{{.Title}}}
|
||||
{%.Title%}
|
||||
</div>
|
||||
</h3>
|
||||
</div>
|
||||
@@ -19,7 +19,7 @@
|
||||
SMTP服务器
|
||||
</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="host" value="{{{.Mail.Host}}}">
|
||||
<input type="text" name="host" value="{%.Mail.Host%}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
@@ -27,7 +27,7 @@
|
||||
端口
|
||||
</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="port" value="{{{if gt .Mail.Port 0}}}{{{.Mail.Port}}}{{{end}}}">
|
||||
<input type="text" name="port" value="{%if gt .Mail.Port 0%}{%.Mail.Port%}{%end%}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
@@ -35,7 +35,7 @@
|
||||
用户名
|
||||
</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="user" value="{{{.Mail.User}}}">
|
||||
<input type="text" name="user" value="{%.Mail.User%}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
@@ -43,25 +43,25 @@
|
||||
密码
|
||||
</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="password" value="{{{.Mail.Password}}}">
|
||||
<input type="text" name="password" value="{%.Mail.Password%}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui primary button">保存</button>
|
||||
{{{if .Mail.Host}}}
|
||||
{%if .Mail.Host%}
|
||||
<a class="ui green button" onclick="clearMailServer()">删除</a>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
<br><br><br>
|
||||
<div>
|
||||
<div class="content">邮箱用户</div><p></p>
|
||||
<div class="fields">
|
||||
{{{range $i, $v := .Mail.MailUsers}}}
|
||||
{%range $i, $v := .Mail.MailUsers%}
|
||||
<div class="field">
|
||||
<div class="ui segment">
|
||||
{{{.Username}}}-{{{.Email}}} <div class="ui blue button" onclick="removeMailUser({{{.Id}}})">删除</div>
|
||||
{%.Username%}-{%.Email%} <div class="ui blue button" onclick="removeMailUser({%.Id%})">删除</div>
|
||||
</div>
|
||||
</div>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -198,4 +198,4 @@
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
@@ -1,13 +1,13 @@
|
||||
<div class="three wide column">
|
||||
<div class="verticalMenu">
|
||||
<div class="ui vertical pointing menu fluid">
|
||||
<a class="{{{if eq .URI "/manage/slack/edit"}}}active teal{{{end}}} item" href="/manage/slack/edit">
|
||||
<a class="{%if eq .URI "/manage/slack/edit"%}active teal{%end%} item" href="/manage/slack/edit">
|
||||
<i class="slack icon"></i> Slack配置
|
||||
</a>
|
||||
<a class="{{{if eq .URI "/manage/mail/edit"}}}active teal{{{end}}} item" href="/manage/mail/edit">
|
||||
<a class="{%if eq .URI "/manage/mail/edit"%}active teal{%end%} item" href="/manage/mail/edit">
|
||||
<i class="slack icon"></i> 邮件配置
|
||||
</a>
|
||||
<a class="{{{if eq .URI "/manage/login-log"}}}active teal{{{end}}} item" href="/manage/login-log">
|
||||
<a class="{%if eq .URI "/manage/login-log"%}active teal{%end%} item" href="/manage/login-log">
|
||||
<i class="slack icon"></i> 登录日志
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
<div class="ui grid">
|
||||
{{{template "manage/menu" .}}}
|
||||
{%template "manage/menu" .%}
|
||||
<div class="twelve wide column">
|
||||
<div class="pageHeader">
|
||||
<div class="segment">
|
||||
<h3 class="ui dividing header">
|
||||
<div class="content">
|
||||
{{{.Title}}}
|
||||
{%.Title%}
|
||||
</div>
|
||||
</h3>
|
||||
</div>
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="content">Slack WebHook URL</div>
|
||||
</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" id="url" value="{{{.Slack.Url}}}">
|
||||
<input type="text" id="url" value="{%.Slack.Url%}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui primary button" @click="updateUrl">保存</div>
|
||||
@@ -27,13 +27,13 @@
|
||||
<div>
|
||||
<div class="content">Slack Channel(配置任务通知时,可选择多Channel)</div><p></p>
|
||||
<div class="fields">
|
||||
{{{range $i, $v := .Slack.Channels}}}
|
||||
{%range $i, $v := .Slack.Channels%}
|
||||
<div class="field">
|
||||
<div class="ui segment">
|
||||
{{{.Name}}} <div class="ui blue button" @click="removeChannel({{{.Id}}})">删除</div>
|
||||
{%.Name%} <div class="ui blue button" @click="removeChannel({%.Id%})">删除</div>
|
||||
</div>
|
||||
</div>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui facebook button" @click="createChannel">新增Channel</div>
|
||||
@@ -85,4 +85,4 @@
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
+59
-54
@@ -1,8 +1,8 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
<div class="ui grid">
|
||||
{{{template "task/menu" .}}}
|
||||
{%template "task/menu" .%}
|
||||
<div class="twelve wide column">
|
||||
{{{if .IsAdmin}}}
|
||||
{%if .IsAdmin%}
|
||||
<div class="pageHeader">
|
||||
<div class="segment">
|
||||
<h3 class="ui dividing header">
|
||||
@@ -15,40 +15,40 @@
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
<form class="ui form">
|
||||
<div class="six fields search">
|
||||
<div class="one wide field">
|
||||
<input type="text" placeholder="任务ID" name="id" value="{{{if gt .Params.Id 0}}}{{{.Params.Id}}}{{{end}}}">
|
||||
<input type="text" placeholder="任务ID" name="id" value="{%if gt .Params.Id 0%}{%.Params.Id%}{%end%}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<input type="text" placeholder="任务名称" name="name" value="{{{.Params.Name}}}">
|
||||
<input type="text" placeholder="任务名称" name="name" value="{%.Params.Name%}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<input type="text" placeholder="标签名称" name="tag" value="{{{.Params.Tag}}}">
|
||||
<input type="text" placeholder="标签名称" name="tag" value="{%.Params.Tag%}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="six fields search">
|
||||
<div class="field">
|
||||
<select name="host_id" id="hostId">
|
||||
<option value="">选择节点</option>
|
||||
{{{range $i, $v := .Hosts}}}
|
||||
<option value="{{{.Id}}}" {{{if eq $.Params.HostId .Id }}} selected {{{end}}} >{{{.Alias}}}-{{{.Name}}}</option>
|
||||
{{{end}}}
|
||||
{%range $i, $v := .Hosts%}
|
||||
<option value="{%.Id%}" {%if eq $.Params.HostId .Id %} selected {%end%} >{%.Alias%}-{%.Name%}</option>
|
||||
{%end%}
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<select name="protocol" id="protocol">
|
||||
<option value="0">执行方式</option>
|
||||
<option value="2" {{{if eq .Params.Protocol 2}}}selected{{{end}}} data-match="host_id" data-validate-type="selectProtocol">SHELL</option>
|
||||
<option value="1" {{{if eq .Params.Protocol 1}}}selected{{{end}}}>HTTP</option>
|
||||
<option value="2" {%if eq .Params.Protocol 2%}selected{%end%} data-match="host_id" data-validate-type="selectProtocol">SHELL</option>
|
||||
<option value="1" {%if eq .Params.Protocol 1%}selected{%end%}>HTTP</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<select name="status">
|
||||
<option value="0">状态</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="1" {%if eq .Params.Status 0%}selected{%end%} >停止</option>
|
||||
<option value="2" {%if eq .Params.Status 1%}selected{%end%}>激活</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
@@ -56,7 +56,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{{if .IsAdmin}}}
|
||||
{%if .IsAdmin%}
|
||||
<div class="field">
|
||||
<select id="batch-operation">
|
||||
<option value="0">批量操作</option>
|
||||
@@ -65,16 +65,16 @@
|
||||
<option value="3">删除</option>
|
||||
</select>
|
||||
</div>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
<br>
|
||||
<table class="ui celled table task-list">
|
||||
<thead>
|
||||
<tr>
|
||||
{{{if .IsAdmin}}}
|
||||
{%if .IsAdmin%}
|
||||
<th>
|
||||
<input type="checkbox" onclick="checkAll(this)" style="width:25px;height: 25px;">
|
||||
</th>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
<th>任务ID</th>
|
||||
<th>任务名称</th>
|
||||
<th>任务类型</th>
|
||||
@@ -90,58 +90,58 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{{range $i, $v := .Tasks}}}
|
||||
{%range $i, $v := .Tasks%}
|
||||
<tr>
|
||||
{{{if $.IsAdmin}}}
|
||||
{%if $.IsAdmin%}
|
||||
<td>
|
||||
<input type="checkbox"
|
||||
class="sub-check"
|
||||
data-id="{{{.Id}}}"
|
||||
data-id="{%.Id%}"
|
||||
style="width:25px;height: 25px;">
|
||||
</td>
|
||||
{{{end}}}
|
||||
<td>{{{.Id}}}</td>
|
||||
<td>{{{.Name}}}</td>
|
||||
<td>{{{if eq .Level 1}}}主任务{{{else}}}子任务{{{end}}}</td>
|
||||
<td>{{{.Tag}}}</td>
|
||||
<td>{{{.Spec}}}</td>
|
||||
<td>{{{if eq .Protocol 1}}} HTTP {{{else if eq .Protocol 2}}} SHELL {{{end}}}</td>
|
||||
<td>{{{if eq .Timeout -1}}}后台运行{{{else if gt .Timeout 0}}}{{{.Timeout}}}秒{{{else}}}不限制{{{end}}}</td>
|
||||
<td>{{{.RetryTimes}}}</td>
|
||||
<td>{{{if gt .Multi 0}}}否{{{else}}}是{{{end}}}</td>
|
||||
{%end%}
|
||||
<td>{%.Id%}</td>
|
||||
<td>{%.Name%}</td>
|
||||
<td>{%if eq .Level 1%}主任务{%else%}子任务{%end%}</td>
|
||||
<td>{%.Tag%}</td>
|
||||
<td>{%.Spec%}</td>
|
||||
<td>{%if eq .Protocol 1%} HTTP {%else if eq .Protocol 2%} SHELL {%end%}</td>
|
||||
<td>{%if eq .Timeout -1%}后台运行{%else if gt .Timeout 0%}{%.Timeout%}秒{%else%}不限制{%end%}</td>
|
||||
<td>{%.RetryTimes%}</td>
|
||||
<td>{%if gt .Multi 0%}否{%else%}是{%end%}</td>
|
||||
<td>
|
||||
{{{range $k, $h := .Hosts}}}
|
||||
{{{$h.Alias}}}<br>
|
||||
{{{end}}}
|
||||
{%range $k, $h := .Hosts%}
|
||||
{%$h.Alias%}<br>
|
||||
{%end%}
|
||||
</td>
|
||||
<td>
|
||||
{{{if eq .Level 1}}}
|
||||
{{{if eq .Status 1}}}<span><i class="checkmark big icon"></i></span>{{{else}}}<span><i class="minus big icon"></i><span>{{{end}}}
|
||||
{{{end}}}
|
||||
{%if eq .Level 1%}
|
||||
{%if eq .Status 1%}<span><i class="checkmark big icon"></i></span>{%else%}<span><i class="minus big icon"></i><span>{%end%}
|
||||
{%end%}
|
||||
</td>
|
||||
<td>
|
||||
<div class="ui buttons operation">
|
||||
{{{if $.IsAdmin}}}
|
||||
<a href="/task/edit/{{{.Id}}}" ><i class="edit big icon" title="编辑"></i></a>
|
||||
{{{if eq .Level 1}}}
|
||||
{{{if eq .Status 1}}}
|
||||
<a href="javascript:void(0);" @click="changeStatus({{{.Id}}},{{{.Status}}})"><i class="pause circle big icon" title="停止"></i></a>
|
||||
{{{else}}}
|
||||
<a href="javascript:void(0);" @click="changeStatus({{{.Id}}},{{{.Status}}})"><i class="play big icon" title="激活"></i></a>
|
||||
{{{end}}}
|
||||
{{{end}}}
|
||||
<a href="javascript:void(0);" @click="remove({{{.Id}}})"><i class="remove big icon" title="删除"></i></a>
|
||||
<a href="javascript:void(0);" @click="run({{{.Id}}})"><i class="rocket big icon" title="手动执行"></i></a>
|
||||
{{{end}}}
|
||||
{%if $.IsAdmin%}
|
||||
<a href="/task/edit/{%.Id%}" ><i class="edit big icon" title="编辑"></i></a>
|
||||
{%if eq .Level 1%}
|
||||
{%if eq .Status 1%}
|
||||
<a href="javascript:void(0);" @click="changeStatus({%.Id%},{%.Status%})"><i class="pause circle big icon" title="停止"></i></a>
|
||||
{%else%}
|
||||
<a href="javascript:void(0);" @click="changeStatus({%.Id%},{%.Status%})"><i class="play big icon" title="激活"></i></a>
|
||||
{%end%}
|
||||
{%end%}
|
||||
<a href="javascript:void(0);" @click="remove({%.Id%})"><i class="remove big icon" title="删除"></i></a>
|
||||
<a href="javascript:void(0);" @click="run({%.Id%})"><i class="rocket big icon" title="手动执行"></i></a>
|
||||
{%end%}
|
||||
|
||||
<a href="/task/log?task_id={{{.Id}}}"><i class="bar chart icon big" title="查看日志"></i></a>
|
||||
<a href="/task/log?task_id={%.Id%}"><i class="bar chart icon big" title="查看日志"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
</tbody>
|
||||
</table>
|
||||
{{{ template "common/pagination" .}}}
|
||||
{% template "common/pagination" .%}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -211,7 +211,12 @@
|
||||
}
|
||||
|
||||
function remove(id, stopReload) {
|
||||
util.post('/task/remove/' + id, {}, function () {
|
||||
var url = '/task/remove/' + id;
|
||||
if (stopReload === undefined) {
|
||||
util.removeConfirm(url);
|
||||
return;
|
||||
}
|
||||
util.post(url, {}, function () {
|
||||
if (stopReload === undefined) {
|
||||
location.reload();
|
||||
}
|
||||
@@ -234,4 +239,4 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
|
||||
+51
-36
@@ -1,4 +1,4 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
<style type="text/css">
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
@@ -10,10 +10,10 @@
|
||||
</style>
|
||||
<div class="ui grid">
|
||||
<!--the vertical menu-->
|
||||
{{{ template "task/menu" . }}}
|
||||
{% template "task/menu" . %}
|
||||
|
||||
<div class="twelve wide column">
|
||||
{{{if .IsAdmin}}}
|
||||
{%if .IsAdmin%}
|
||||
<div class="pageHeader">
|
||||
<div class="segment">
|
||||
<h3 class="ui dividing header">
|
||||
@@ -23,26 +23,26 @@
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
<form class="ui form">
|
||||
<div class="six fields search">
|
||||
<div class="field">
|
||||
<input type="text" placeholder="任务ID" name="task_id" value="{{{if gt .Params.TaskId 0}}}{{{.Params.TaskId}}}{{{end}}}">
|
||||
<input type="text" placeholder="任务ID" name="task_id" value="{%if gt .Params.TaskId 0%}{%.Params.TaskId%}{%end%}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<select name="protocol" id="protocol">
|
||||
<option value="0">执行方式</option>
|
||||
<option value="2" {{{if eq .Params.Protocol 2}}}selected{{{end}}} data-match="host_id" data-validate-type="selectProtocol">SHELL</option>
|
||||
<option value="1" {{{if eq .Params.Protocol 1}}}selected{{{end}}}>HTTP</option>
|
||||
<option value="2" {%if eq .Params.Protocol 2%}selected{%end%} data-match="host_id" data-validate-type="selectProtocol">SHELL</option>
|
||||
<option value="1" {%if eq .Params.Protocol 1%}selected{%end%}>HTTP</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<select name="status">
|
||||
<option value="0">状态</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="3" {{{if eq .Params.Status 2}}}selected{{{end}}}>成功</option>
|
||||
<option value="4" {{{if eq .Params.Status 3}}}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="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">
|
||||
@@ -65,47 +65,54 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{{range $i, $v := .Logs}}}
|
||||
{%range $i, $v := .Logs%}
|
||||
<tr>
|
||||
<td><a href="/task?id={{{.TaskId}}}">{{{.TaskId}}}</a></td>
|
||||
<td>{{{.Name}}}</td>
|
||||
<td>{{{.Spec}}}</td>
|
||||
<td>{{{if eq .Protocol 1}}} HTTP {{{else if eq .Protocol 2}}} SHELL {{{end}}}</td>
|
||||
<td>{{{.RetryTimes}}}</td>
|
||||
<td>{{{unescape .Hostname}}}</td>
|
||||
<td><a href="/task?id={%.TaskId%}">{%.TaskId%}</a></td>
|
||||
<td>{%.Name%}</td>
|
||||
<td>{%.Spec%}</td>
|
||||
<td>{%if eq .Protocol 1%} HTTP {%else if eq .Protocol 2%} SHELL {%end%}</td>
|
||||
<td>{%.RetryTimes%}</td>
|
||||
<td>{%unescape .Hostname%}</td>
|
||||
<td>
|
||||
{{{if and (ne .Status 3) (ne .Status 4)}}}
|
||||
{{{if gt .TotalTime 0}}}{{{.TotalTime}}}秒{{{else}}}1秒{{{end}}}<br>
|
||||
开始时间: {{{.StartTime.Format "2006-01-02 15:04:05" }}}<br>
|
||||
{{{if ne .Status 1}}}
|
||||
结束时间: {{{.EndTime.Format "2006-01-02 15:04:05" }}}
|
||||
{{{end}}}
|
||||
{{{end}}}
|
||||
{%if and (ne .Status 3) (ne .Status 4)%}
|
||||
{%if gt .TotalTime 0%}{%.TotalTime%}秒{%else%}1秒{%end%}<br>
|
||||
开始时间: {%.StartTime.Format "2006-01-02 15:04:05" %}<br>
|
||||
{%if ne .Status 1%}
|
||||
结束时间: {%.EndTime.Format "2006-01-02 15:04:05" %}
|
||||
{%end%}
|
||||
{%end%}
|
||||
</td>
|
||||
<td>
|
||||
{{{if eq .Status 2}}}
|
||||
{%if eq .Status 2%}
|
||||
成功
|
||||
{{{else if eq .Status 1}}}
|
||||
{%else if eq .Status 1%}
|
||||
<span style="color:green">执行中</span>
|
||||
{{{else if eq .Status 0}}}
|
||||
{%else if eq .Status 0%}
|
||||
<span style="color:red">失败</span>
|
||||
{{{else if eq .Status 3}}}
|
||||
{%else if eq .Status 3%}
|
||||
<span style="color:#4499EE">取消</span>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
</td>
|
||||
<td>
|
||||
{{{if or (eq .Status 2) (eq .Status 0)}}}
|
||||
{%if or (eq .Status 2) (eq .Status 0)%}
|
||||
<button class="ui small primary button"
|
||||
onclick="showResult('{{{.Name}}}', '{{{.Command}}}', '{{{.Result}}}')"
|
||||
onclick="showResult('{%.Name%}', '{%.Command%}', '{%.Result%}')"
|
||||
>查看结果
|
||||
</button>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
|
||||
|
||||
{%if $.IsAdmin%}
|
||||
{%if and (eq .Status 1) (eq .Protocol 2) %}
|
||||
<button class="ui small blue button" onclick="stopTask({%.Id%}, {%.TaskId%})">停止任务</button>
|
||||
{%end%}
|
||||
{%end%}
|
||||
</td>
|
||||
</tr>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
</tbody>
|
||||
</table>
|
||||
{{{ template "common/pagination" .}}}
|
||||
{% template "common/pagination" .%}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -148,6 +155,14 @@
|
||||
}).modal('refresh').modal('show');
|
||||
}
|
||||
|
||||
function stopTask(id, taskId) {
|
||||
util.confirm("确定要停止任务吗", function () {
|
||||
util.post("/task/log/stop/", {id: id, task_id:taskId}, function (code, message) {
|
||||
swal('提示', message, 'info');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function clearLog() {
|
||||
util.confirm("确定要删除所有日志吗?", function() {
|
||||
util.post("/task/log/clear",{}, function() {
|
||||
@@ -156,4 +171,4 @@
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
@@ -1,10 +1,10 @@
|
||||
<div class="three wide column">
|
||||
<div class="verticalMenu">
|
||||
<div class="ui vertical pointing menu fluid">
|
||||
<a class="{{{if eq .URI "/task"}}}active teal{{{end}}} item" href="/task">
|
||||
<a class="{%if eq .URI "/task"%}active teal{%end%} item" href="/task">
|
||||
<i class="tasks icon"></i> 定时任务列表
|
||||
</a>
|
||||
<a class="item {{{if eq .URI "/task/log"}}}active teal{{{end}}} " href="/task/log">
|
||||
<a class="item {%if eq .URI "/task/log"%}active teal{%end%} " href="/task/log">
|
||||
<i class="bar chart icon"></i> 定时任务日志
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
<div class="ui grid">
|
||||
{{{template "task/menu" .}}}
|
||||
{%template "task/menu" .%}
|
||||
<div class="twelve wide column">
|
||||
<div class="pageHeader">
|
||||
<div class="segment">
|
||||
<h3 class="ui dividing header">
|
||||
<div class="content">
|
||||
{{{.Title}}}
|
||||
{%.Title%}
|
||||
</div>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<form class="ui form fluid vertical segment">
|
||||
<input type="hidden" name="id" value="{{{.Task.Id}}}">
|
||||
<input type="hidden" name="id" value="{%.Task.Id%}">
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>
|
||||
<div class="content">任务名称</div>
|
||||
</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="name" placeholder="任务名称" value="{{{.Task.Name}}}">
|
||||
<input type="text" name="name" placeholder="任务名称" value="{%.Task.Name%}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<div class="content">标签名称</div>
|
||||
</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="tag" placeholder="标签用于任务分类" value="{{{.Task.Tag}}}">
|
||||
<input type="text" name="tag" placeholder="标签用于任务分类" value="{%.Task.Tag%}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,9 +41,9 @@
|
||||
任务类型新增后不能变更
|
||||
</div>
|
||||
</label>
|
||||
<select name="level" id="level" {{{if .Task}}}disabled="disabled"{{{end}}}>
|
||||
<option value="1" {{{if .Task}}} {{{if eq .Task.Level 1}}}selected{{{end}}} {{{end}}}>主任务</option>
|
||||
<option value="2" {{{if .Task}}} {{{if eq .Task.Level 2}}}selected{{{end}}} {{{end}}}>子任务</option>
|
||||
<select name="level" id="level" {%if .Task%}disabled="disabled"{%end%}>
|
||||
<option value="1" {%if .Task%} {%if eq .Task.Level 1%}selected{%end%} {%end%}>主任务</option>
|
||||
<option value="2" {%if .Task%} {%if eq .Task.Level 2%}selected{%end%} {%end%}>子任务</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,8 +58,8 @@
|
||||
</div>
|
||||
</label>
|
||||
<select name="dependency_status" id="dependency_status">
|
||||
<option value="1" {{{if .Task}}} {{{if eq .Task.DependencyStatus 1}}}selected{{{end}}} {{{end}}}>强依赖</option>
|
||||
<option value="2" {{{if .Task}}} {{{if eq .Task.DependencyStatus 2}}}selected{{{end}}} {{{end}}}>弱依赖</option>
|
||||
<option value="1" {%if .Task%} {%if eq .Task.DependencyStatus 1%}selected{%end%} {%end%}>强依赖</option>
|
||||
<option value="2" {%if .Task%} {%if eq .Task.DependencyStatus 2%}selected{%end%} {%end%}>弱依赖</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
@@ -71,7 +71,7 @@
|
||||
</div>
|
||||
</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="dependency_task_id" placeholder="可选" value="{{{.Task.DependencyTaskId}}}">
|
||||
<input type="text" name="dependency_task_id" placeholder="可选" value="{%.Task.DependencyTaskId%}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,7 +83,7 @@
|
||||
</div>
|
||||
</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="spec" value="{{{.Task.Spec}}}" placeholder="秒 分 时 天 月 周"/>
|
||||
<input type="text" name="spec" value="{%.Task.Spec%}" placeholder="秒 分 时 天 月 周"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -92,9 +92,9 @@
|
||||
<div class="field">
|
||||
<label>执行方式</label>
|
||||
<select name="protocol" id="protocol">
|
||||
<option value="2" {{{if .Task}}} {{{if eq .Task.Protocol 2}}}selected{{{end}}} {{{end}}}
|
||||
<option value="2" {%if .Task%} {%if eq .Task.Protocol 2%}selected{%end%} {%end%}
|
||||
data-validate-type="selectProtocol">SHELL</option>
|
||||
<option value="1" {{{if .Task}}} {{{if eq .Task.Protocol 1}}}selected{{{end}}} {{{end}}}>HTTP</option>
|
||||
<option value="1" {%if .Task%} {%if eq .Task.Protocol 1%}selected{%end%} {%end%}>HTTP</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -102,47 +102,64 @@
|
||||
<div class="field">
|
||||
<label>选择任务节点</label>
|
||||
<div id="hostId">
|
||||
{{{range $i, $v := .Hosts}}}
|
||||
{%range $i, $v := .Hosts%}
|
||||
<label>
|
||||
<input type="checkbox" value="{{{.Id}}}" {{{if $.Task}}}{{{if $v.Selected}}} checked {{{end}}}{{{end}}} style="width:25px;height: 25px;">{{{.Alias}}}-{{{.Name}}}
|
||||
{{{if (HostFormat $i) }}}<br>{{{end}}}
|
||||
<input type="checkbox" value="{%.Id%}" {%if $.Task%}{%if $v.Selected%} checked {%end%}{%end%} style="width:25px;height: 25px;">{%.Alias%}-{%.Name%}
|
||||
{%if (HostFormat $i) %}<br>{%end%}
|
||||
</label>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
</div> <br> <a class="ui blue button" href="/host/create" target="_blank">添加节点</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="two fields" id="http-method" style="display: none">
|
||||
<div class="field">
|
||||
<label>请求方法</label>
|
||||
<select name="http_method">
|
||||
<option value="1" {%if .Task%} {%if eq .Task.HttpMethod 1%}selected{%end%} {%end%}>GET</option>
|
||||
<option value="2" {%if .Task%} {%if eq .Task.HttpMethod 2%}selected{%end%} {%end%}
|
||||
data-validate-type="selectProtocol">POST</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>命令</label>
|
||||
<textarea rows="5" name="command" placeholder="请输入系统命令" id="command">{{{.Task.Command}}}</textarea>
|
||||
<textarea rows="5" name="command" placeholder="请输入系统命令" id="command">{%.Task.Command%}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="three fields">
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>任务超时时间(秒, 0-86400)</label>
|
||||
<input type="text" name="timeout" placeholder="默认0, 不限制" value="{{{if .Task}}} {{{.Task.Timeout}}} {{{else}}}0{{{end}}}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>任务失败重试次数 (0-10)</label>
|
||||
<input type="text" name="retry_times" placeholder="默认0, 不重试" value="{{{if .Task}}} {{{.Task.RetryTimes}}} {{{else}}}0{{{end}}}">
|
||||
<input type="text" name="timeout" placeholder="默认0, 不限制" value="{%if .Task%} {%.Task.Timeout%} {%else%}0{%end%}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>允许多实例同时运行</label>
|
||||
<select name="multi">
|
||||
<option value="2" {{{if .Task}}} {{{if eq .Task.Multi 0}}}selected{{{end}}} {{{end}}}>否</option>
|
||||
<option value="1"{{{if .Task}}} {{{if eq .Task.Multi 1}}}selected{{{end}}} {{{end}}}>是</option>
|
||||
<option value="2" {%if .Task%} {%if eq .Task.Multi 0%}selected{%end%} {%end%}>否</option>
|
||||
<option value="1"{%if .Task%} {%if eq .Task.Multi 1%}selected{%end%} {%end%}>是</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>任务失败重试次数 (0-10)</label>
|
||||
<input type="text" name="retry_times" placeholder="默认0, 不重试" value="{%if .Task%} {%.Task.RetryTimes%} {%else%}0{%end%}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>任务失败重试间隔时间 (秒, 0-3600)</label>
|
||||
<input type="text" name="retry_interval" placeholder="默认0, 执行默认重试策略" value="{%if .Task%} {%.Task.RetryInterval%} {%else%}0{%end%}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>任务通知</label>
|
||||
<select name="notify_status" id="task-status">
|
||||
<option value="1"{{{if .Task}}} {{{if eq .Task.NotifyStatus 0}}}selected{{{end}}} {{{end}}}>不通知</option>
|
||||
<option value="2" {{{if .Task}}} {{{if eq .Task.NotifyStatus 1}}}selected{{{end}}} {{{end}}}>失败通知</option>
|
||||
<option value="3" {{{if .Task}}} {{{if eq .Task.NotifyStatus 2}}}selected{{{end}}} {{{end}}}>执行结束通知</option>
|
||||
<option value="1"{%if .Task%} {%if eq .Task.NotifyStatus 0%}selected{%end%} {%end%}>不通知</option>
|
||||
<option value="2" {%if .Task%} {%if eq .Task.NotifyStatus 1%}selected{%end%} {%end%}>失败通知</option>
|
||||
<option value="3" {%if .Task%} {%if eq .Task.NotifyStatus 2%}selected{%end%} {%end%}>执行结束通知</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -150,9 +167,9 @@
|
||||
<div class="field" >
|
||||
<label>通知类型</label>
|
||||
<select name="notify_type">
|
||||
<option value="1"{{{if .Task}}} {{{if eq .Task.NotifyType 0}}}selected{{{end}}} {{{end}}}>请选择</option>
|
||||
<option value="2" {{{if .Task}}} {{{if eq .Task.NotifyType 1}}}selected{{{end}}} {{{end}}}>邮件</option>
|
||||
<option value="3" {{{if .Task}}} {{{if eq .Task.NotifyType 2}}}selected{{{end}}} {{{end}}}>Slack</option>
|
||||
<option value="1"{%if .Task%} {%if eq .Task.NotifyType 0%}selected{%end%} {%end%}>请选择</option>
|
||||
<option value="2" {%if .Task%} {%if eq .Task.NotifyType 1%}selected{%end%} {%end%}>邮件</option>
|
||||
<option value="3" {%if .Task%} {%if eq .Task.NotifyType 2%}selected{%end%} {%end%}>Slack</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -160,7 +177,7 @@
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>备注</label>
|
||||
<textarea rows="5" name="remark" placeholder="任务备注信息">{{{.Task.Remark}}}</textarea>
|
||||
<textarea rows="5" name="remark" placeholder="任务备注信息">{%.Task.Remark%}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui primary submit button">保存</div> <a class="ui button" onclick="location.href='/task';">取消</a>
|
||||
@@ -239,11 +256,11 @@
|
||||
}
|
||||
|
||||
function showNotify() {
|
||||
var notifyStatus = {{{.Task.NotifyStatus}}};
|
||||
var notifyStatus = {%.Task.NotifyStatus%};
|
||||
if (notifyStatus > 0) {
|
||||
$('#task-notify-type').show();
|
||||
}
|
||||
var notifyReceiverIds = '{{{.Task.NotifyReceiverId}}}'.split(',');
|
||||
var notifyReceiverIds = '{%.Task.NotifyReceiverId%}'.split(',');
|
||||
changeNotify(notifyReceiverIds);
|
||||
}
|
||||
|
||||
@@ -292,10 +309,12 @@
|
||||
var protocol = $('#protocol').val();
|
||||
if (protocol == 2) {
|
||||
$('#hostField').show();
|
||||
$('#http-method').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
$('#hostField').hide();
|
||||
$('#http-method').show();
|
||||
}
|
||||
|
||||
$('.ui.checkbox')
|
||||
@@ -423,6 +442,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
retryInterval: {
|
||||
identifier : 'retry_interval',
|
||||
rules: [
|
||||
{
|
||||
type : 'integer[0..3600]',
|
||||
prompt : '重试间隔时间0-3600'
|
||||
}
|
||||
]
|
||||
},
|
||||
remark: {
|
||||
identifier : 'remark',
|
||||
rules: [
|
||||
@@ -437,4 +465,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
@@ -1,4 +1,4 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
<div class="ui small modal">
|
||||
<div class="header">修改密码</div>
|
||||
<div class="content">
|
||||
@@ -70,4 +70,4 @@
|
||||
inline : true
|
||||
});
|
||||
</script>
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
@@ -1,4 +1,4 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
<div class="ui small modal">
|
||||
<div class="header">修改密码</div>
|
||||
<div class="content">
|
||||
@@ -25,7 +25,7 @@
|
||||
$('.ui.form').form(
|
||||
{
|
||||
onSuccess: function(event, fields) {
|
||||
util.post('/user/editPassword/{{{.Id}}}', fields, function(code, message) {
|
||||
util.post('/user/editPassword/{%.Id%}', fields, function(code, message) {
|
||||
swal("操作成功", '修改成功', 'success');
|
||||
location.href = "/user"
|
||||
});
|
||||
@@ -55,4 +55,4 @@
|
||||
inline : true
|
||||
});
|
||||
</script>
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
+19
-19
@@ -1,7 +1,7 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
|
||||
<div class="ui grid">
|
||||
{{{ template "user/menu" . }}}
|
||||
{% template "user/menu" . %}
|
||||
|
||||
<div class="twelve wide column">
|
||||
<div class="pageHeader">
|
||||
@@ -28,30 +28,30 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{{range $i, $v := .Users}}}
|
||||
{%range $i, $v := .Users%}
|
||||
<tr>
|
||||
<td>{{{.Id}}}</td>
|
||||
<td>{{{.Name}}}</td>
|
||||
<td>{{{.Email}}}</td>
|
||||
<td>{{{if .IsAdmin}}}管理员{{{else}}}普通用户{{{end}}}</td>
|
||||
<td>{{{if .Status}}}启用{{{else}}}禁用{{{end}}}</td>
|
||||
<td>{%.Id%}</td>
|
||||
<td>{%.Name%}</td>
|
||||
<td>{%.Email%}</td>
|
||||
<td>{%if .IsAdmin%}管理员{%else%}普通用户{%end%}</td>
|
||||
<td>{%if .Status%}启用{%else%}禁用{%end%}</td>
|
||||
<td class="operation">
|
||||
<a class="ui purple button" href="/user/edit/{{{.Id}}}">编辑</a>
|
||||
{{{if eq .Status 1}}}
|
||||
<button class="ui button red" onclick="changeStatus({{{.Id}}},{{{.Status}}})">禁用</button>
|
||||
{{{else}}}
|
||||
<button class="ui button twitter" onclick="changeStatus({{{.Id}}},{{{.Status}}})">启用</button>
|
||||
{{{end}}}
|
||||
<a href="/user/editPassword/{{{.Id}}}">
|
||||
<a class="ui purple button" href="/user/edit/{%.Id%}">编辑</a>
|
||||
{%if eq .Status 1%}
|
||||
<button class="ui button red" onclick="changeStatus({%.Id%},{%.Status%})">禁用</button>
|
||||
{%else%}
|
||||
<button class="ui button twitter" onclick="changeStatus({%.Id%},{%.Status%})">启用</button>
|
||||
{%end%}
|
||||
<a href="/user/editPassword/{%.Id%}">
|
||||
<button class="ui button facebook">修改密码</button>
|
||||
</a>
|
||||
<button class="ui positive button" onclick="util.removeConfirm('/user/remove/{{{.Id}}}')">删除</button>
|
||||
<button class="ui positive button" onclick="util.removeConfirm('/user/remove/{%.Id%}')">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
</tbody>
|
||||
</table>
|
||||
{{{ template "common/pagination" .}}}
|
||||
{% template "common/pagination" .%}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -70,4 +70,4 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
@@ -1,4 +1,4 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
<div class="ui small modal">
|
||||
<div class="header">用户登录</div>
|
||||
<div class="content">
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
{{{.Captcha.CreateHtml}}}
|
||||
{%.Captcha.CreateHtml%}
|
||||
<input type="text" name="captcha" placeholder="验证码">
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,4 +71,4 @@
|
||||
inline : true
|
||||
});
|
||||
</script>
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="four wide column">
|
||||
<div class="verticalMenu">
|
||||
<div class="ui vertical pointing menu fluid">
|
||||
<a class="{{{if eq .URI "/user"}}}active teal{{{end}}} item" href="/user">
|
||||
<a class="{%if eq .URI "/user"%}active teal{%end%} item" href="/user">
|
||||
<i class="user icon"></i> 用户列表
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{{ template "common/header" . }}}
|
||||
{% template "common/header" . %}
|
||||
|
||||
<div class="ui grid">
|
||||
{{{ template "user/menu" . }}}
|
||||
{% template "user/menu" . %}
|
||||
|
||||
<div class="twelve wide column">
|
||||
<div class="pageHeader">
|
||||
@@ -14,22 +14,22 @@
|
||||
</div>
|
||||
</div>
|
||||
<form class="ui form fluid vertical segment">
|
||||
<input type="hidden" name="id" value="{{{.User.Id}}}">
|
||||
<input type="hidden" name="id" value="{%.User.Id%}">
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>用户名</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="name" value="{{{.User.Name}}}" placeholder="请输入用户名">
|
||||
<input type="text" name="name" value="{%.User.Name%}" placeholder="请输入用户名">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>邮箱</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="email" value="{{{.User.Email}}}" placeholder="请输入邮箱">
|
||||
<input type="text" name="email" value="{%.User.Email%}" placeholder="请输入邮箱">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{{if eq .User.Id 0}}}
|
||||
{%if eq .User.Id 0%}
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>密码</label>
|
||||
@@ -41,20 +41,20 @@
|
||||
<input type="password" name="confirm_password" placeholder="请再次输入密码">
|
||||
</div>
|
||||
</div>
|
||||
{{{end}}}
|
||||
{%end%}
|
||||
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>角色</label>
|
||||
<input type="radio" name="is_admin" value="0" {{{if eq .User.IsAdmin 0}}}checked{{{end}}}> 普通用户
|
||||
<input type="radio" name="is_admin" value="1" {{{if eq .User.IsAdmin 1}}}checked{{{end}}}> 管理员
|
||||
<input type="radio" name="is_admin" value="0" {%if eq .User.IsAdmin 0%}checked{%end%}> 普通用户
|
||||
<input type="radio" name="is_admin" value="1" {%if eq .User.IsAdmin 1%}checked{%end%}> 管理员
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>状态</label>
|
||||
<input type="radio" name="status" value="1" {{{if eq .User.Status 1}}}checked{{{end}}}> 启用
|
||||
<input type="radio" name="status" value="1" {%if eq .User.Status 1%}checked{%end%}> 启用
|
||||
<input type="radio" name="status" value="0"
|
||||
{{{if eq .User.Status 0}}}checked{{{end}}}
|
||||
{%if eq .User.Status 0%}checked{%end%}
|
||||
> 禁用
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,4 +139,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
{{{ template "common/footer" . }}}
|
||||
{% template "common/footer" . %}
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# set -x -u
|
||||
# 上传二进制包到七牛
|
||||
|
||||
if [[ -z $QINIU_ACCESS_KEY || -z $QINIU_SECRET_KEY || -z $QINIU_URL ]];then
|
||||
echo 'QINIU_ACCESS_KEY | QINIU_SECRET_KEY | QINIU_URL is need'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 打包
|
||||
for i in linux darwin windows
|
||||
do
|
||||
./build.sh -p $i
|
||||
if [[ $? != 0 ]];then
|
||||
break
|
||||
fi
|
||||
./build_node.sh -p $i
|
||||
if [[ $? != 0 ]];then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# 身份认证
|
||||
qrsctl login $QINIU_ACCESS_KEY $QINIU_SECRET_KEY
|
||||
|
||||
# 上传
|
||||
for i in `ls gocron*.gz gocron*.zip`
|
||||
do
|
||||
# 上传文件 qrsctl put bucket key srcFile
|
||||
KEY=gocron/$i
|
||||
qrsctl put github $KEY $i
|
||||
if [[ $? != 0 ]];then
|
||||
break
|
||||
fi
|
||||
echo "刷新七牛CDN-" $QINIU_URL/$KEY
|
||||
qrsctl cdn/refresh $QINIU_URL/$KEY
|
||||
rm $i
|
||||
done
|
||||
|
||||
echo '打包并上传成功'
|
||||
Reference in New Issue
Block a user