V2.7 增加agent执行器
This commit is contained in:
8
actuator/main.go
Normal file
8
actuator/main.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package main
|
||||
|
||||
//任务执行器
|
||||
//
|
||||
|
||||
func main() {
|
||||
|
||||
}
|
||||
25
agent/common/protocol.go
Normal file
25
agent/common/protocol.go
Normal file
@@ -0,0 +1,25 @@
|
||||
/************************************************************
|
||||
** @Description: common
|
||||
** @Author: george hao
|
||||
** @Date: 2018-11-29 11:14
|
||||
** @Last Modified by: george hao
|
||||
** @Last Modified time: 2018-11-29 11:14
|
||||
*************************************************************/
|
||||
package common
|
||||
|
||||
//配置开始 注释查看配置文件
|
||||
type Conf struct {
|
||||
Version string
|
||||
AppMode string
|
||||
LogLevel string
|
||||
ServerName string
|
||||
ServerId int
|
||||
TcpPort int
|
||||
TcpIp string
|
||||
GroupId string
|
||||
RegisterUrl string
|
||||
UpdateStatusUrl string
|
||||
IpType int
|
||||
}
|
||||
|
||||
var ExitChan = make(chan int, 1)
|
||||
24
agent/config/conf.ini
Normal file
24
agent/config/conf.ini
Normal file
@@ -0,0 +1,24 @@
|
||||
# GOLBAL
|
||||
# dev prod
|
||||
AppMode = dev
|
||||
Version = 1.0.0
|
||||
# ALL,DEBUG,INFO,NOTICE,WARN,ERROR,FATAL
|
||||
LogLevel = ALL
|
||||
# 执行器配置
|
||||
# auto-自动起名,或者自己起名
|
||||
ServerName = agent-10.32.40.165-1564
|
||||
# 启动后回写
|
||||
ServerId = 7
|
||||
# 端口,必须配置!!
|
||||
TcpPort = 1564
|
||||
# auto-自动获取
|
||||
TcpIp = 10.32.40.165
|
||||
# Ip地址是外网还是内网,1-外网,0-内网,若填写TcpIp则本项配置无意义
|
||||
IpType = 0
|
||||
# 添加的执行器属于分组Id,默认为1
|
||||
GroupId = 1
|
||||
|
||||
# 以下配置必填,地址格式:http://yourdomain/server/apisave
|
||||
RegisterUrl = http://localhost:8081/server/apisave
|
||||
UpdateStatusUrl = http://localhost:8081/server/apistatus
|
||||
|
||||
59
agent/main.go
Normal file
59
agent/main.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/george518/PPGo_Job/agent/server"
|
||||
"log"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
//文件配置路径
|
||||
var configFilePath string
|
||||
|
||||
func initArgs() {
|
||||
//server -c ./configpath
|
||||
//defaultPath := "/Users/haodaquan/golang/src/github.com/george518/PPGo_Job/agent/config/conf.ini"
|
||||
defaultPath := "./config/conf.ini"
|
||||
flag.StringVar(&configFilePath, "c", defaultPath, "config file path request")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func initEnv() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
}
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
//初始化线程
|
||||
initEnv()
|
||||
|
||||
//配置文件路径
|
||||
initArgs()
|
||||
|
||||
//加载配置
|
||||
if err = server.InitConfig(configFilePath); err != nil {
|
||||
goto ERR
|
||||
}
|
||||
|
||||
server.NLog("INFO", "配置文件读取完毕...")
|
||||
|
||||
//应用关闭监控
|
||||
server.ListenSignal()
|
||||
|
||||
//自动注册
|
||||
if err = server.Register(); err != nil {
|
||||
goto ERR
|
||||
}
|
||||
|
||||
server.NLog("INFO", "自动注册完成...")
|
||||
|
||||
server.NLog("INFO", "agent is running...")
|
||||
//监听
|
||||
if err = server.RpcRun(); err != nil {
|
||||
goto ERR
|
||||
}
|
||||
|
||||
ERR:
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
30
agent/run.sh
Normal file
30
agent/run.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
# @Author: haodaquan
|
||||
# @Date: 2017-06-29 17:44:45
|
||||
# @Last Modified by: haodaquan
|
||||
# @Last Modified time: 2019-07-03 17:44:45
|
||||
|
||||
|
||||
case $1 in
|
||||
start)
|
||||
nohup ./ppgo_agent 2>&1 >> info_agent.log 2>&1 /dev/null &
|
||||
echo "服务已启动..."
|
||||
sleep 1
|
||||
;;
|
||||
stop)
|
||||
killall ppgo_agent
|
||||
echo "服务已停止..."
|
||||
sleep 1
|
||||
;;
|
||||
restart)
|
||||
killall ppgo_agent
|
||||
sleep 1
|
||||
nohup ./ppgo_agent 2>&1 >> info_agent.log 2>&1 /dev/null &
|
||||
echo "服务已重启..."
|
||||
sleep 1
|
||||
;;
|
||||
*)
|
||||
echo "$0 {start|stop|restart}"
|
||||
exit 4
|
||||
;;
|
||||
esac
|
||||
39
agent/server/config.go
Normal file
39
agent/server/config.go
Normal file
@@ -0,0 +1,39 @@
|
||||
/************************************************************
|
||||
** @Description: server
|
||||
** @Author: george hao
|
||||
** @Date: 2018-11-29 11:13
|
||||
** @Last Modified by: george hao
|
||||
** @Last Modified time: 2018-11-29 11:13
|
||||
*************************************************************/
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/george518/PPGo_Job/agent/common"
|
||||
"github.com/go-ini/ini"
|
||||
)
|
||||
|
||||
var C = new(common.Conf)
|
||||
var ConfPath string
|
||||
|
||||
func InitConfig(path string) error {
|
||||
|
||||
Cfg, err := ini.Load(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ConfPath = path
|
||||
err = Cfg.MapTo(C)
|
||||
return err
|
||||
}
|
||||
|
||||
func SaveConfig(key string, value string) error {
|
||||
Cfg, err := ini.Load(ConfPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Cfg.Section("").Key(key).SetValue(value)
|
||||
Cfg.SaveTo(ConfPath)
|
||||
InitConfig(ConfPath)
|
||||
return nil
|
||||
}
|
||||
139
agent/server/job.go
Normal file
139
agent/server/job.go
Normal file
@@ -0,0 +1,139 @@
|
||||
/************************************************************
|
||||
** @Description: job
|
||||
** @Author: george hao
|
||||
** @Date: 2019-06-24 15:14
|
||||
** @Last Modified by: george hao
|
||||
** @Last Modified time: 2019-06-24 15:14
|
||||
*************************************************************/
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/astaxie/beego/logs"
|
||||
. "github.com/george518/PPGo_Job/jobs"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
//执行句柄map
|
||||
var CmdMap sync.Map
|
||||
|
||||
func SetCmdMap(key string, cmd *exec.Cmd) {
|
||||
if _, ok := CmdMap.Load(key); ok {
|
||||
Counter.Store(key, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func GetCmdMap(key string) *exec.Cmd {
|
||||
if v, ok := CmdMap.Load(key); ok {
|
||||
return v.(*exec.Cmd)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RestJobFromTask(task *models.Task, serverId int) (*Job, error) {
|
||||
|
||||
if task.Id < 1 {
|
||||
return nil, fmt.Errorf("ToJob: 缺少id")
|
||||
}
|
||||
|
||||
if task.ServerIds == "" {
|
||||
return nil, fmt.Errorf("任务执行失败,找不到执行的服务器")
|
||||
}
|
||||
|
||||
job := ResetCommandJob(task.Id, serverId, task.TaskName, task.Command)
|
||||
job.Task = task
|
||||
job.Concurrent = task.Concurrent == 1
|
||||
job.ServerId = serverId
|
||||
job.ServerName = "执行器"
|
||||
|
||||
return job, nil
|
||||
}
|
||||
|
||||
func ResetCommandJob(id int, serverId int, name string, command string) *Job {
|
||||
job := &Job{
|
||||
Id: id,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
job.JobKey = libs.JobKey(id, serverId)
|
||||
job.RunFunc = func(timeout time.Duration) (jobResult *JobResult) {
|
||||
bufOut := new(bytes.Buffer)
|
||||
bufErr := new(bytes.Buffer)
|
||||
//cmd := exec.Command("/bin/bash", "-c", command)
|
||||
var cmd *exec.Cmd
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd = exec.Command("CMD", "/C", command)
|
||||
} else {
|
||||
cmd = exec.Command("sh", "-c", command)
|
||||
}
|
||||
cmd.Stdout = bufOut
|
||||
cmd.Stderr = bufErr
|
||||
cmd.Start()
|
||||
err, isTimeout := runCmdWithTimeout(cmd, timeout)
|
||||
|
||||
jobResult = new(JobResult)
|
||||
jobResult.ErrMsg = libs.GbkAsUtf8(bufErr.String())
|
||||
jobResult.OutMsg = libs.GbkAsUtf8(bufOut.String())
|
||||
jobResult.IsOk = true
|
||||
if err != nil {
|
||||
jobResult.IsOk = false
|
||||
}
|
||||
|
||||
jobResult.IsTimeout = isTimeout
|
||||
|
||||
return
|
||||
}
|
||||
return job
|
||||
}
|
||||
func runCmdWithTimeout(cmd *exec.Cmd, timeout time.Duration) (error, bool) {
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
done <- cmd.Wait()
|
||||
}()
|
||||
|
||||
var err error
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
logs.Warn(fmt.Sprintf("任务执行时间超过%d秒,进程将被强制杀掉: %d", int(timeout/time.Second), cmd.Process.Pid))
|
||||
go func() {
|
||||
<-done // 读出上面的goroutine数据,避免阻塞导致无法退出
|
||||
}()
|
||||
if err = cmd.Process.Kill(); err != nil {
|
||||
logs.Error(fmt.Sprintf("进程无法杀掉: %d, 错误信息: %s", cmd.Process.Pid, err))
|
||||
}
|
||||
return err, true
|
||||
case err = <-done:
|
||||
return err, false
|
||||
}
|
||||
}
|
||||
|
||||
func Run(j *Job) *JobResult {
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logs.Error(err, "\n", string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
logs.Debug(fmt.Sprintf("开始执行任务: %d", j.JobKey))
|
||||
|
||||
j.Status++
|
||||
defer func() {
|
||||
j.Status--
|
||||
}()
|
||||
|
||||
timeout := time.Duration(time.Hour * 24)
|
||||
if j.Task.Timeout > 0 {
|
||||
timeout = time.Second * time.Duration(j.Task.Timeout)
|
||||
}
|
||||
|
||||
return j.RunFunc(timeout)
|
||||
}
|
||||
41
agent/server/logs.go
Normal file
41
agent/server/logs.go
Normal file
@@ -0,0 +1,41 @@
|
||||
/************************************************************
|
||||
** @Description: log
|
||||
** @Author: haodaquan
|
||||
** @Date: 2018-08-22 23:00
|
||||
** @Last Modified by: haodaquan
|
||||
** @Last Modified time: 2018-08-22 23:00
|
||||
*************************************************************/
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Env string
|
||||
|
||||
func init() {
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
}
|
||||
|
||||
//http相关
|
||||
func WriteLog(r *http.Request, t time.Time, match string, pattern string) {
|
||||
|
||||
if C.AppMode != "prod" {
|
||||
d := time.Now().Sub(t)
|
||||
l := fmt.Sprintf("[ACCESS] | % -10s | % -40s | % -16s | % -10s | % -40s |",
|
||||
r.Method, r.URL.Path, d.String(), match, pattern)
|
||||
log.Println(l)
|
||||
}
|
||||
}
|
||||
|
||||
//系统运行相关
|
||||
func NLog(level string, value ...interface{}) {
|
||||
if strings.Contains(C.LogLevel, level) || C.LogLevel == "ALL" {
|
||||
log.Println("["+level+"]", value)
|
||||
return
|
||||
}
|
||||
}
|
||||
104
agent/server/notify.go
Normal file
104
agent/server/notify.go
Normal file
@@ -0,0 +1,104 @@
|
||||
/************************************************************
|
||||
** @Description: notify
|
||||
** @Author: george hao
|
||||
** @Date: 2019-06-26 15:17
|
||||
** @Last Modified by: george hao
|
||||
** @Last Modified time: 2019-06-26 15:17
|
||||
*************************************************************/
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//启动时注册
|
||||
func Register() error {
|
||||
//获取本机ip以及端口 todo ip合法性判断
|
||||
if C.TcpIp == "auto" {
|
||||
tcpIp := libs.GetHostIp(C.IpType)
|
||||
if tcpIp == "" {
|
||||
return fmt.Errorf("无法获取本机IP,请手工在配置文件里设置")
|
||||
}
|
||||
SaveConfig("TcpIp", tcpIp)
|
||||
}
|
||||
param := make(map[string]string, 0)
|
||||
if C.ServerName == "auto" {
|
||||
serverName := "agent-" + C.TcpIp + "-" + strconv.Itoa(C.TcpPort)
|
||||
SaveConfig("ServerName", serverName)
|
||||
}
|
||||
|
||||
param["server_ip"] = C.TcpIp
|
||||
param["port"] = strconv.Itoa(C.TcpPort)
|
||||
param["server_name"] = C.ServerName
|
||||
param["detail"] = "自动注册执行器"
|
||||
param["connection_type"] = "2"
|
||||
param["group_id"] = C.GroupId
|
||||
|
||||
if C.RegisterUrl == "" {
|
||||
return fmt.Errorf("自动注册地址配置错误")
|
||||
}
|
||||
body, err := libs.HttpGet(C.RegisterUrl, param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m := make(map[string]interface{})
|
||||
err = json.Unmarshal([]byte(body), &m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := m["status"]; ok {
|
||||
if m["status"] == float64(0) {
|
||||
//回写serverId
|
||||
serverId := int(m["message"].(float64))
|
||||
SaveConfig("ServerId", strconv.Itoa(serverId))
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("自动注册失败:%v", m["message"])
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("自动注册失败")
|
||||
}
|
||||
|
||||
//程序异常退出的通知
|
||||
func Close() error {
|
||||
|
||||
param := make(map[string]string, 0)
|
||||
param["server_ip"] = C.TcpIp
|
||||
param["port"] = strconv.Itoa(C.TcpPort)
|
||||
param["status"] = "1"
|
||||
|
||||
if C.UpdateStatusUrl == "" {
|
||||
return fmt.Errorf("执行器退出通知异常,请到系统中修改状态")
|
||||
}
|
||||
body, err := libs.HttpGet(C.UpdateStatusUrl, param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m := make(map[string]interface{})
|
||||
err = json.Unmarshal([]byte(body), &m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := m["status"]; ok {
|
||||
if m["status"] == float64(0) {
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("执行器退出通知异常:%v", m["message"])
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("执行器退出通知异常:未知原因")
|
||||
}
|
||||
|
||||
//心跳机制
|
||||
func Heartbeat() error {
|
||||
return nil
|
||||
}
|
||||
38
agent/server/service.go
Normal file
38
agent/server/service.go
Normal file
@@ -0,0 +1,38 @@
|
||||
/************************************************************
|
||||
** @Description: service
|
||||
** @Author: george hao
|
||||
** @Date: 2019-06-26 15:27
|
||||
** @Last Modified by: george hao
|
||||
** @Last Modified time: 2019-06-26 15:27
|
||||
*************************************************************/
|
||||
package server
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/rpc"
|
||||
"net/rpc/jsonrpc"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//初始化路由
|
||||
func init() {
|
||||
rpc.RegisterName("RpcTask", new(RpcTask))
|
||||
rpc.RegisterName("HeartBeat", new(RpcTask))
|
||||
}
|
||||
|
||||
func RpcRun() error {
|
||||
|
||||
listener, err := net.Listen("tcp", ":"+strconv.Itoa(C.TcpPort))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//注意ServerCodec是个方法,不是接口
|
||||
go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
|
||||
//go rpc.ServeConn(conn)
|
||||
}
|
||||
}
|
||||
41
agent/server/signal.go
Normal file
41
agent/server/signal.go
Normal file
@@ -0,0 +1,41 @@
|
||||
/************************************************************
|
||||
** @Description: server
|
||||
** @Author: george hao
|
||||
** @Date: 2018-11-29 11:24
|
||||
** @Last Modified by: george hao
|
||||
** @Last Modified time: 2018-11-29 11:24
|
||||
*************************************************************/
|
||||
package server
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
//监听关闭状态
|
||||
func ListenSignal() {
|
||||
//创建监听退出chan
|
||||
c := make(chan os.Signal)
|
||||
//监听指定信号 ctrl+c kill
|
||||
signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)
|
||||
|
||||
go func() {
|
||||
for s := range c {
|
||||
switch s {
|
||||
case syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2:
|
||||
NLog("NOTICE", " Ready to quit close type ", s)
|
||||
//TODO 异常警报,汇报状态
|
||||
if err := Close(); err != nil {
|
||||
NLog("ERROR", err.Error())
|
||||
} else {
|
||||
NLog("NOTICE", " 执行器安全关闭...")
|
||||
}
|
||||
os.Exit(0)
|
||||
default:
|
||||
NLog("NOTICE", " close type ", s)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
47
agent/server/task.go
Normal file
47
agent/server/task.go
Normal file
@@ -0,0 +1,47 @@
|
||||
/************************************************************
|
||||
** @Description: task
|
||||
** @Author: george hao
|
||||
** @Date: 2019-06-24 13:22
|
||||
** @Last Modified by: george hao
|
||||
** @Last Modified time: 2019-06-24 13:22
|
||||
*************************************************************/
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/george518/PPGo_Job/jobs"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
)
|
||||
|
||||
type RpcTask struct {
|
||||
}
|
||||
|
||||
type RpcResult struct {
|
||||
Status int
|
||||
Message string
|
||||
}
|
||||
|
||||
//Execute once
|
||||
func (r *RpcTask) RunTask(task *models.Task, Result *jobs.JobResult) error {
|
||||
server_id := C.ServerId
|
||||
job, err := RestJobFromTask(task, server_id)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
*Result = *(Run(job))
|
||||
return nil
|
||||
}
|
||||
|
||||
//Kill execution
|
||||
func (r *RpcTask) KillCommand(task models.Task, reply *RpcResult) error {
|
||||
reply.Status = 200
|
||||
reply.Message = "Ok kill " + task.TaskName
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RpcTask) HeartBeat(ping string, reply *RpcResult) error {
|
||||
reply.Status = 200
|
||||
reply.Message = ping + " pong"
|
||||
logs.Info(ping)
|
||||
return nil
|
||||
}
|
||||
28
agent/test/conf/conf.go
Normal file
28
agent/test/conf/conf.go
Normal file
@@ -0,0 +1,28 @@
|
||||
/************************************************************
|
||||
** @Description: conf
|
||||
** @Author: george hao
|
||||
** @Date: 2019-06-27 09:49
|
||||
** @Last Modified by: george hao
|
||||
** @Last Modified time: 2019-06-27 09:49
|
||||
*************************************************************/
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/george518/PPGo_Job/agent/server"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//获取配置,修改配置
|
||||
loadconfig()
|
||||
}
|
||||
|
||||
func loadconfig() {
|
||||
|
||||
path := "/Users/haodaquan/golang/src/github.com/george518/PPGo_Job/actuator/config/conf.ini"
|
||||
server.InitConfig(path)
|
||||
logs.Info(server.C.TcpIp, server.ConfPath)
|
||||
server.SaveConfig("TcpIp", "10.32.33.22")
|
||||
logs.Info(server.C.TcpIp, server.ConfPath)
|
||||
|
||||
}
|
||||
18
agent/test/ip/ip.go
Normal file
18
agent/test/ip/ip.go
Normal file
@@ -0,0 +1,18 @@
|
||||
/************************************************************
|
||||
** @Description: ip
|
||||
** @Author: george hao
|
||||
** @Date: 2019-06-27 09:22
|
||||
** @Last Modified by: george hao
|
||||
** @Last Modified time: 2019-06-27 09:22
|
||||
*************************************************************/
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logs.Info(libs.PublicIp())
|
||||
|
||||
}
|
||||
13
common/protocol.go
Normal file
13
common/protocol.go
Normal file
@@ -0,0 +1,13 @@
|
||||
/************************************************************
|
||||
** @Description: protol
|
||||
** @Author: george hao
|
||||
** @Date: 2019-06-27 15:33
|
||||
** @Last Modified by: george hao
|
||||
** @Last Modified time: 2019-06-27 15:33
|
||||
*************************************************************/
|
||||
package common
|
||||
|
||||
type RpcResult struct {
|
||||
Status int
|
||||
Message string
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
AppName = PPGo_Job2
|
||||
HTTPPort = 8080
|
||||
HTTPPort = 8081
|
||||
RunMode = dev
|
||||
SessionOn = true
|
||||
|
||||
version= V2.6
|
||||
version= V2.7
|
||||
|
||||
# 允许同时运行的任务数
|
||||
jobs.pool = 1000
|
||||
@@ -19,7 +19,7 @@ db.host = 127.0.0.1
|
||||
db.user = root
|
||||
db.password = "123456"
|
||||
db.port = 3306
|
||||
db.name = ppgo_job2
|
||||
db.name = cron
|
||||
db.prefix = pp_
|
||||
db.timezone = Asia/Shanghai
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/axgle/mahonia"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"strconv"
|
||||
@@ -100,7 +99,10 @@ func (self *BaseController) Auth() {
|
||||
self.actionName != "loginin" &&
|
||||
self.actionName != "apistart" &&
|
||||
self.actionName != "apitask" &&
|
||||
self.actionName != "apipause") {
|
||||
self.actionName != "apipause" &&
|
||||
self.actionName != "apisave" &&
|
||||
self.actionName != "apistatus" &&
|
||||
self.actionName != "apiget") {
|
||||
self.redirect(beego.URLFor("LoginController.Login"))
|
||||
}
|
||||
}
|
||||
@@ -336,7 +338,7 @@ type serverList struct {
|
||||
func serverLists(authStr string, adminId int) (sls []serverList) {
|
||||
serverGroup := serverGroupLists(authStr, adminId)
|
||||
Filters := make([]interface{}, 0)
|
||||
Filters = append(Filters, "status", 0)
|
||||
Filters = append(Filters, "status__in", []int{0, 1})
|
||||
|
||||
Result, _ := models.TaskServerGetList(1, 1000, Filters...)
|
||||
for k, v := range serverGroup {
|
||||
@@ -354,16 +356,3 @@ func serverLists(authStr string, adminId int) (sls []serverList) {
|
||||
}
|
||||
return sls
|
||||
}
|
||||
|
||||
func gbkAsUtf8(str string) string {
|
||||
srcDecoder := mahonia.NewDecoder("gbk")
|
||||
desDecoder := mahonia.NewDecoder("utf-8")
|
||||
resStr := srcDecoder.ConvertString(str)
|
||||
_, resBytes, _ := desDecoder.Translate([]byte(resStr), true)
|
||||
return string(resBytes)
|
||||
}
|
||||
|
||||
//任务识别码
|
||||
func jobKey(taskId, serverId int) int {
|
||||
return taskId*10000 + serverId
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -43,7 +42,6 @@ func (self *LoginController) LoginIn() {
|
||||
password := strings.TrimSpace(self.GetString("password"))
|
||||
if username != "" && password != "" {
|
||||
user, err := models.AdminGetByName(username)
|
||||
fmt.Println(user)
|
||||
if err != nil || user.Password != libs.Md5([]byte(password+user.Salt)) {
|
||||
self.ajaxMsg("帐号或密码错误", MSG_ERR)
|
||||
} else if user.Status == -1 {
|
||||
|
||||
@@ -8,16 +8,12 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"github.com/linxiaozhi/go-telnet"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ServerController struct {
|
||||
@@ -25,13 +21,13 @@ type ServerController struct {
|
||||
}
|
||||
|
||||
func (self *ServerController) List() {
|
||||
self.Data["pageTitle"] = "资源管理"
|
||||
self.Data["pageTitle"] = "执行资源管理"
|
||||
self.Data["serverGroup"] = serverGroupLists(self.serverGroups, self.userId)
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *ServerController) Add() {
|
||||
self.Data["pageTitle"] = "新增服务器资源"
|
||||
self.Data["pageTitle"] = "新增执行资源"
|
||||
self.Data["serverGroup"] = serverGroupLists(self.serverGroups, self.userId)
|
||||
self.display()
|
||||
}
|
||||
@@ -92,7 +88,7 @@ func (self *ServerController) GetServerByGroupId() {
|
||||
}
|
||||
|
||||
func (self *ServerController) Edit() {
|
||||
self.Data["pageTitle"] = "编辑服务器资源"
|
||||
self.Data["pageTitle"] = "编辑执行资源"
|
||||
|
||||
id, _ := self.GetInt("id", 0)
|
||||
server, _ := models.TaskServerGetById(id)
|
||||
@@ -136,12 +132,12 @@ func (self *ServerController) AjaxTestServer() {
|
||||
if server.ConnectionType == 0 {
|
||||
if server.Type == 0 {
|
||||
//密码登录
|
||||
err = RemoteCommandByPassword(server)
|
||||
err = libs.RemoteCommandByPassword(server)
|
||||
}
|
||||
|
||||
if server.Type == 1 {
|
||||
//密钥登录
|
||||
err = RemoteCommandByKey(server)
|
||||
err = libs.RemoteCommandByKey(server)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -151,7 +147,7 @@ func (self *ServerController) AjaxTestServer() {
|
||||
} else if server.ConnectionType == 1 {
|
||||
if server.Type == 0 {
|
||||
//密码登录
|
||||
err = RemoteCommandByTelnetPassword(server)
|
||||
err = libs.RemoteCommandByTelnetPassword(server)
|
||||
} else {
|
||||
self.ajaxMsg("Telnet方式暂不支持密钥登陆!", MSG_ERR)
|
||||
}
|
||||
@@ -160,114 +156,19 @@ func (self *ServerController) AjaxTestServer() {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("Success", MSG_OK)
|
||||
} else if server.ConnectionType == 2 {
|
||||
|
||||
if err := libs.RemoteAgent(server); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
} else {
|
||||
self.ajaxMsg("Success", MSG_OK)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
self.ajaxMsg("未知连接方式", MSG_ERR)
|
||||
}
|
||||
|
||||
func RemoteCommandByTelnetPassword(servers *models.TaskServer) error {
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", servers.ServerIp, servers.Port)
|
||||
conn, err := gote.DialTimeout("tcp", addr, time.Second*10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
buf := make([]byte, 4096)
|
||||
_, err = conn.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Write([]byte(servers.ServerAccount + "\r\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Write([]byte(servers.Password + "\r\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
str := gbkAsUtf8(string(buf[:]))
|
||||
|
||||
if strings.Contains(str, ">") {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Errorf("连接失败!")
|
||||
}
|
||||
|
||||
func RemoteCommandByPassword(servers *models.TaskServer) error {
|
||||
var (
|
||||
auth []ssh.AuthMethod
|
||||
addr string
|
||||
clientConfig *ssh.ClientConfig
|
||||
)
|
||||
|
||||
auth = make([]ssh.AuthMethod, 0)
|
||||
auth = append(auth, ssh.Password(servers.Password))
|
||||
|
||||
clientConfig = &ssh.ClientConfig{
|
||||
User: servers.ServerAccount,
|
||||
Auth: auth,
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
addr = fmt.Sprintf("%s:%d", servers.ServerIp, servers.Port)
|
||||
client, err := ssh.Dial("tcp", addr, clientConfig)
|
||||
if err == nil {
|
||||
defer client.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func RemoteCommandByKey(servers *models.TaskServer) error {
|
||||
key, err := ioutil.ReadFile(servers.PrivateKeySrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signer, err := ssh.ParsePrivateKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", servers.ServerIp, servers.Port)
|
||||
config := &ssh.ClientConfig{
|
||||
User: servers.ServerAccount,
|
||||
Auth: []ssh.AuthMethod{
|
||||
// Use the PublicKeys method for remote authentication.
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
//HostKeyCallback: ssh.FixedHostKey(hostKey),
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
client, err := ssh.Dial("tcp", addr, config)
|
||||
if err == nil {
|
||||
client.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *ServerController) Copy() {
|
||||
self.Data["pageTitle"] = "复制服务器资源"
|
||||
|
||||
@@ -347,9 +248,14 @@ func (self *ServerController) AjaxSave() {
|
||||
|
||||
func (self *ServerController) AjaxDel() {
|
||||
id, _ := self.GetInt("id")
|
||||
|
||||
if id == 1 {
|
||||
self.ajaxMsg("默认分组id=1,禁止删除", MSG_ERR)
|
||||
}
|
||||
|
||||
server, _ := models.TaskServerGetById(id)
|
||||
server.UpdateTime = time.Now().Unix()
|
||||
server.Status = 1
|
||||
server.Status = 2
|
||||
server.Id = id
|
||||
|
||||
//TODO 查询服务器是否用于定时任务
|
||||
@@ -374,18 +280,19 @@ func (self *ServerController) Table() {
|
||||
|
||||
serverName := strings.TrimSpace(self.GetString("serverName"))
|
||||
StatusText := []string{
|
||||
"正常",
|
||||
"<font color='red'>禁用</font>",
|
||||
"<i class='fa fa-refresh' style='color:#5FB878'></i>",
|
||||
"<i class='fa fa-ban' style='color:#FF5722'></i>",
|
||||
}
|
||||
//
|
||||
//loginType := [2]string{
|
||||
// "密码",
|
||||
// "密钥",
|
||||
//}
|
||||
|
||||
loginType := [2]string{
|
||||
"密码",
|
||||
"密钥",
|
||||
}
|
||||
|
||||
connectionType := [2]string{
|
||||
connectionType := [3]string{
|
||||
"SSH",
|
||||
"Telnet",
|
||||
"Agent",
|
||||
}
|
||||
|
||||
serverGroup := serverGroupLists(self.serverGroups, self.userId)
|
||||
@@ -393,7 +300,8 @@ func (self *ServerController) Table() {
|
||||
self.pageSize = limit
|
||||
//查询条件
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "status", 0)
|
||||
ids := []int{0, 1}
|
||||
filters = append(filters, "status__in", ids)
|
||||
|
||||
groupsIds := make([]int, 0)
|
||||
if self.userId != 1 {
|
||||
@@ -425,17 +333,128 @@ func (self *ServerController) Table() {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["connection_type"] = connectionType[v.ConnectionType]
|
||||
row["server_name"] = v.ServerName
|
||||
row["server_name"] = StatusText[v.Status] + " " + v.ServerName
|
||||
row["detail"] = v.Detail
|
||||
if serverGroup[v.GroupId] == "" {
|
||||
v.GroupId = 0
|
||||
}
|
||||
row["ip_port"] = v.ServerIp + ":" + strconv.Itoa(v.Port)
|
||||
row["group_name"] = serverGroup[v.GroupId]
|
||||
row["type"] = loginType[v.Type]
|
||||
//row["type"] = loginType[v.Type]
|
||||
row["status"] = v.Status
|
||||
row["status_text"] = StatusText[v.Status]
|
||||
list[k] = row
|
||||
}
|
||||
|
||||
self.ajaxList("成功", MSG_OK, count, list)
|
||||
}
|
||||
|
||||
//以下函数为执行器接口
|
||||
//注册
|
||||
func (self *ServerController) ApiSave() {
|
||||
//唯一确定值 ip+port
|
||||
serverIp := strings.TrimSpace(self.GetString("server_ip"))
|
||||
port, _ := self.GetInt("port")
|
||||
|
||||
if serverIp == "" || port == 0 {
|
||||
self.ajaxMsg("执行器和端口号必填", MSG_ERR)
|
||||
}
|
||||
|
||||
defaultActName := "agent-" + serverIp + "-" + strconv.Itoa(port)
|
||||
|
||||
id := models.TaskServerForActuator(serverIp, port)
|
||||
if id == 0 {
|
||||
//新增
|
||||
server := new(models.TaskServer)
|
||||
server.ConnectionType, _ = self.GetInt("connection_type", 3)
|
||||
server.ServerName = strings.TrimSpace(self.GetString("server_name", defaultActName))
|
||||
server.ServerAccount = strings.TrimSpace(self.GetString("server_account", "agent"))
|
||||
server.ServerOuterIp = strings.TrimSpace(self.GetString("server_outer_ip", ""))
|
||||
server.ServerIp = strings.TrimSpace(self.GetString("server_ip"))
|
||||
server.PrivateKeySrc = strings.TrimSpace(self.GetString("private_key_src", ""))
|
||||
server.PublicKeySrc = strings.TrimSpace(self.GetString("public_key_src", ""))
|
||||
server.Password = strings.TrimSpace(self.GetString("password", "agent"))
|
||||
|
||||
server.Detail = strings.TrimSpace(self.GetString("detail", ""))
|
||||
server.Type, _ = self.GetInt("type", 0)
|
||||
server.Port, _ = self.GetInt("port")
|
||||
server.GroupId, _ = self.GetInt("group_id", 0)
|
||||
server.Status = 0
|
||||
|
||||
server.CreateTime = time.Now().Unix()
|
||||
server.UpdateTime = time.Now().Unix()
|
||||
server.Status = 0
|
||||
serverId, err := models.TaskServerAdd(server)
|
||||
if err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg(serverId, MSG_OK)
|
||||
} else {
|
||||
//修改状态
|
||||
server, _ := models.TaskServerGetById(id)
|
||||
server.UpdateTime = time.Now().Unix()
|
||||
server.Status, _ = self.GetInt("status", 0)
|
||||
if err := server.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg(id, MSG_OK)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//检测0-正常,1-异常,2-删除
|
||||
func (self *ServerController) ApiStatus() {
|
||||
//唯一确定值 ip+port
|
||||
serverId := strings.TrimSpace(self.GetString("server_ip"))
|
||||
port, _ := self.GetInt("port")
|
||||
status, _ := self.GetInt("status", 0)
|
||||
|
||||
if serverId == "" || port == 0 {
|
||||
self.ajaxMsg("执行器和端口号必填", MSG_ERR)
|
||||
}
|
||||
|
||||
id := models.TaskServerForActuator(serverId, port)
|
||||
if id == 0 {
|
||||
self.ajaxMsg("执行器不存在", MSG_ERR)
|
||||
}
|
||||
|
||||
if status != 0 && status != 1 {
|
||||
status = 0
|
||||
}
|
||||
|
||||
server, _ := models.TaskServerGetById(id)
|
||||
server.UpdateTime = time.Now().Unix()
|
||||
server.Status = status
|
||||
server.Id = id
|
||||
|
||||
logs.Info(server)
|
||||
|
||||
//TODO 查询执行器是否正在使用中
|
||||
if err := server.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg(id, MSG_OK)
|
||||
}
|
||||
|
||||
//获取 不检测执行器状态
|
||||
func (self *ServerController) ApiGet() {
|
||||
//唯一确定值 ip+port
|
||||
serverId := strings.TrimSpace(self.GetString("server_ip"))
|
||||
port, _ := self.GetInt("port")
|
||||
|
||||
if serverId == "" || port == 0 {
|
||||
self.ajaxMsg("执行器和端口号必填", MSG_ERR)
|
||||
}
|
||||
|
||||
id := models.TaskServerForActuator(serverId, port)
|
||||
if id == 0 {
|
||||
self.ajaxMsg("执行器不存在", MSG_ERR)
|
||||
}
|
||||
|
||||
server, err := models.TaskServerGetById(id)
|
||||
|
||||
if err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
} else {
|
||||
self.ajaxMsg(server, MSG_OK)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
|
||||
"strconv"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
@@ -55,7 +53,6 @@ func (self *ServerGroupController) AjaxSave() {
|
||||
|
||||
servergroup_id, _ := self.GetInt("id")
|
||||
|
||||
fmt.Println(servergroup_id)
|
||||
if servergroup_id == 0 {
|
||||
//新增
|
||||
servergroup.CreateTime = time.Now().Unix()
|
||||
|
||||
@@ -9,6 +9,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -115,11 +116,18 @@ func (self *TaskController) Copy() {
|
||||
if err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
|
||||
if task.Status == 1 {
|
||||
self.ajaxMsg("运行状态无法编辑任务,请先暂停任务", MSG_ERR)
|
||||
}
|
||||
self.Data["task"] = task
|
||||
|
||||
self.Data["adminInfo"] = AllAdminInfo("")
|
||||
|
||||
// 分组列表
|
||||
self.Data["taskGroup"] = taskGroupLists(self.taskGroups, self.userId)
|
||||
self.Data["serverGroup"] = serverLists(self.serverGroups, self.userId)
|
||||
self.Data["isAdmin"] = self.userId
|
||||
var notifyUserIds []int
|
||||
if task.NotifyUserIds != "0" {
|
||||
notifyUserIdsStr := strings.Split(task.NotifyUserIds, ",")
|
||||
@@ -128,7 +136,33 @@ func (self *TaskController) Copy() {
|
||||
notifyUserIds = append(notifyUserIds, i)
|
||||
}
|
||||
}
|
||||
|
||||
self.Data["notify_user_ids"] = notifyUserIds
|
||||
|
||||
server_ids := strings.Split(task.ServerIds, ",")
|
||||
var server_ids_arr []int
|
||||
for _, sv := range server_ids {
|
||||
i, _ := strconv.Atoi(sv)
|
||||
server_ids_arr = append(server_ids_arr, i)
|
||||
}
|
||||
|
||||
self.Data["service_ids"] = server_ids_arr
|
||||
|
||||
notifyTplList, _, err := models.NotifyTplGetByTplTypeList(task.NotifyType)
|
||||
tplList := make([]map[string]interface{}, len(notifyTplList))
|
||||
|
||||
if err == nil {
|
||||
for k, v := range notifyTplList {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["tpl_name"] = v.TplName
|
||||
row["tpl_type"] = v.TplType
|
||||
tplList[k] = row
|
||||
}
|
||||
}
|
||||
|
||||
self.Data["notifyTpl"] = tplList
|
||||
|
||||
self.display()
|
||||
}
|
||||
|
||||
@@ -157,12 +191,12 @@ func (self *TaskController) Detail() {
|
||||
|
||||
serverName := ""
|
||||
if task.ServerIds == "0" {
|
||||
serverName = "本地服务器"
|
||||
serverName = "本地服务器 <br>"
|
||||
} else {
|
||||
serverIdSli := strings.Split(task.ServerIds, ",")
|
||||
for _, v := range serverIdSli {
|
||||
if v == "0" {
|
||||
serverName = "本地服务器 "
|
||||
serverName = "本地服务器 <br>"
|
||||
}
|
||||
}
|
||||
servers, n := models.TaskServerGetByIds(task.ServerIds)
|
||||
@@ -170,9 +204,9 @@ func (self *TaskController) Detail() {
|
||||
for _, server := range servers {
|
||||
fmt.Println(server.Status)
|
||||
if server.Status != 0 {
|
||||
serverName += server.ServerName + "【无效】 "
|
||||
serverName += server.ServerName + " <i class='fa fa-ban' style='color:#FF5722'></i> <br/> "
|
||||
} else {
|
||||
serverName += server.ServerName + " "
|
||||
serverName += server.ServerName + " <br/> "
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -180,6 +214,12 @@ func (self *TaskController) Detail() {
|
||||
}
|
||||
}
|
||||
|
||||
//执行策略
|
||||
self.Data["ServerType"] = "同时执行"
|
||||
if task.ServerType == 1 {
|
||||
self.Data["ServerType"] = "轮询执行"
|
||||
}
|
||||
|
||||
//任务分组
|
||||
groupName := "默认分组"
|
||||
if task.GroupId > 0 {
|
||||
@@ -242,6 +282,8 @@ func (self *TaskController) AjaxSave() {
|
||||
task.Command = strings.TrimSpace(self.GetString("command"))
|
||||
task.Timeout, _ = self.GetInt("timeout")
|
||||
task.IsNotify, _ = self.GetInt("is_notify")
|
||||
task.ServerType, _ = self.GetInt("server_type")
|
||||
|
||||
task.NotifyType, _ = self.GetInt("notify_type")
|
||||
task.NotifyTplId, _ = self.GetInt("notify_tpl_id")
|
||||
task.NotifyUserIds = strings.TrimSpace(self.GetString("notify_user_ids"))
|
||||
@@ -287,6 +329,7 @@ func (self *TaskController) AjaxSave() {
|
||||
task.CronSpec = strings.TrimSpace(self.GetString("cron_spec"))
|
||||
task.Command = strings.TrimSpace(self.GetString("command"))
|
||||
task.Timeout, _ = self.GetInt("timeout")
|
||||
task.ServerType, _ = self.GetInt("server_type")
|
||||
task.IsNotify, _ = self.GetInt("is_notify")
|
||||
task.NotifyType, _ = self.GetInt("notify_type")
|
||||
task.NotifyTplId, _ = self.GetInt("notify_tpl_id")
|
||||
@@ -402,7 +445,7 @@ func (self *TaskController) AjaxPause() {
|
||||
|
||||
for _, server_id := range TaskServerIdsArr {
|
||||
server_id_int, _ := strconv.Atoi(server_id)
|
||||
jobKey := jobKey(task.Id, server_id_int)
|
||||
jobKey := libs.JobKey(task.Id, server_id_int)
|
||||
jobs.RemoveJob(jobKey)
|
||||
}
|
||||
|
||||
@@ -477,7 +520,7 @@ func (self *TaskController) AjaxBatchPause() {
|
||||
|
||||
for _, server_id := range TaskServerIdsArr {
|
||||
server_id_int, _ := strconv.Atoi(server_id)
|
||||
jobKey := jobKey(task.Id, server_id_int)
|
||||
jobKey := libs.JobKey(task.Id, server_id_int)
|
||||
jobs.RemoveJob(jobKey)
|
||||
}
|
||||
if err == nil {
|
||||
@@ -507,7 +550,7 @@ func (self *TaskController) AjaxBatchDel() {
|
||||
|
||||
for _, server_id := range TaskServerIdsArr {
|
||||
server_id_int, _ := strconv.Atoi(server_id)
|
||||
jobKey := jobKey(task.Id, server_id_int)
|
||||
jobKey := libs.JobKey(task.Id, server_id_int)
|
||||
jobs.RemoveJob(jobKey)
|
||||
}
|
||||
models.TaskDel(id)
|
||||
@@ -688,8 +731,16 @@ func (self *TaskController) Table() {
|
||||
row["status"] = v.Status
|
||||
row["pre_time"] = beego.Date(time.Unix(v.PrevTime, 0), "Y-m-d H:i:s")
|
||||
row["execute_times"] = v.ExecuteTimes
|
||||
row["cron_spec"] = v.CronSpec
|
||||
|
||||
TaskServerIdsArr := strings.Split(v.ServerIds, ",")
|
||||
serverId := 0
|
||||
if len(TaskServerIdsArr) > 1 {
|
||||
serverId, _ = strconv.Atoi(TaskServerIdsArr[0])
|
||||
}
|
||||
jobskey := libs.JobKey(v.Id, serverId)
|
||||
e := jobs.GetEntryById(jobskey)
|
||||
|
||||
e := jobs.GetEntryById(v.Id)
|
||||
if e != nil {
|
||||
row["next_time"] = beego.Date(e.Next, "Y-m-d H:i:s")
|
||||
row["prev_time"] = "-"
|
||||
@@ -850,7 +901,7 @@ func (self *TaskController) ApiPause() {
|
||||
|
||||
for _, server_id := range TaskServerIdsArr {
|
||||
server_id_int, _ := strconv.Atoi(server_id)
|
||||
jobKey := jobKey(task.Id, server_id_int)
|
||||
jobKey := libs.JobKey(task.Id, server_id_int)
|
||||
jobs.RemoveJob(jobKey)
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ func (self *TaskLogController) Table() {
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["task_id"] = jobKey(v.TaskId, v.ServerId)
|
||||
row["task_id"] = libs.JobKey(v.TaskId, v.ServerId)
|
||||
row["start_time"] = beego.Date(time.Unix(v.CreateTime, 0), "Y-m-d H:i:s")
|
||||
row["process_time"] = float64(v.ProcessTime) / 1000
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ func AddJob(spec string, job *Job) bool {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
if GetEntryById(job.jobKey) != nil {
|
||||
if GetEntryById(job.JobKey) != nil {
|
||||
return false
|
||||
}
|
||||
err := mainCron.AddJob(spec, job)
|
||||
@@ -47,7 +47,7 @@ func AddJob(spec string, job *Job) bool {
|
||||
func RemoveJob(jobKey int) {
|
||||
mainCron.RemoveJob(func(e *cron.Entry) bool {
|
||||
if v, ok := e.Job.(*Job); ok {
|
||||
if v.jobKey == jobKey {
|
||||
if v.JobKey == jobKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func GetEntryById(jobKey int) *cron.Entry {
|
||||
entries := mainCron.Entries()
|
||||
for _, e := range entries {
|
||||
if v, ok := e.Job.(*Job); ok {
|
||||
if v.jobKey == jobKey {
|
||||
if v.JobKey == jobKey {
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
500
jobs/job.go
500
jobs/job.go
@@ -9,11 +9,18 @@ package jobs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/rpc"
|
||||
"net/rpc/jsonrpc"
|
||||
"os/exec"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"runtime"
|
||||
@@ -22,27 +29,54 @@ import (
|
||||
|
||||
"encoding/json"
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/axgle/mahonia"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"github.com/george518/PPGo_Job/notify"
|
||||
"github.com/linxiaozhi/go-telnet"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type Job struct {
|
||||
jobKey int // jobId = id*10000+serverId
|
||||
id int // taskID
|
||||
logId int64 // 日志记录ID
|
||||
serverId int //服务器信息
|
||||
serverName string //服务器名称
|
||||
name string // 任务名称
|
||||
task *models.Task // 任务对象
|
||||
runFunc func(time.Duration) (string, string, error, bool) // 执行函数
|
||||
status int // 任务状态,大于0表示正在执行中
|
||||
JobKey int // jobId = id*10000+serverId
|
||||
Id int // taskID
|
||||
LogId int64 // 日志记录ID
|
||||
ServerId int // 执行器信息
|
||||
ServerName string // 执行器名称
|
||||
ServerType int // 执行器类型,2-agent 1-telnet 0-ssh
|
||||
Name string // 任务名称
|
||||
Task *models.Task // 任务对象
|
||||
RunFunc func(time.Duration) *JobResult // 执行函数
|
||||
Status int // 任务状态,大于0表示正在执行中
|
||||
Concurrent bool // 同一个任务是否允许并行执行
|
||||
}
|
||||
|
||||
type JobResult struct {
|
||||
OutMsg string
|
||||
ErrMsg string
|
||||
IsOk bool
|
||||
IsTimeout bool
|
||||
}
|
||||
|
||||
//调度计数器
|
||||
var Counter sync.Map
|
||||
|
||||
func GetCounter(key string) int {
|
||||
if v, ok := Counter.LoadOrStore(key, 0); ok {
|
||||
n := v.(int)
|
||||
return n
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func SetCounter(key string) {
|
||||
if v, ok := Counter.Load(key); ok {
|
||||
n := v.(int)
|
||||
m := n + 1
|
||||
if n > 1000 {
|
||||
m = 0
|
||||
}
|
||||
Counter.Store(key, m)
|
||||
}
|
||||
}
|
||||
|
||||
func NewJobFromTask(task *models.Task) ([]*Job, error) {
|
||||
if task.Id < 1 {
|
||||
return nil, fmt.Errorf("ToJob: 缺少id")
|
||||
@@ -53,24 +87,26 @@ func NewJobFromTask(task *models.Task) ([]*Job, error) {
|
||||
}
|
||||
|
||||
TaskServerIdsArr := strings.Split(task.ServerIds, ",")
|
||||
|
||||
jobArr := make([]*Job, 0)
|
||||
|
||||
for _, server_id := range TaskServerIdsArr {
|
||||
if server_id == "0" {
|
||||
//本地执行
|
||||
job := NewCommandJob(task.Id, 0, task.TaskName, task.Command)
|
||||
job.task = task
|
||||
job.Concurrent = task.Concurrent == 1
|
||||
job.serverId = 0
|
||||
job.serverName = "本地服务器"
|
||||
job.Task = task
|
||||
job.Concurrent = false
|
||||
if task.Concurrent == 1 {
|
||||
job.Concurrent = true
|
||||
}
|
||||
//job.Concurrent = task.Concurrent == 1
|
||||
job.ServerId = 0
|
||||
job.ServerName = "本地服务器"
|
||||
jobArr = append(jobArr, job)
|
||||
} else {
|
||||
server_id_int, _ := strconv.Atoi(server_id)
|
||||
//远程执行
|
||||
server, _ := models.TaskServerGetById(server_id_int)
|
||||
|
||||
if server.Status == 1 {
|
||||
if server.Status == 2 {
|
||||
fmt.Println("服务器已禁用")
|
||||
continue
|
||||
}
|
||||
@@ -79,29 +115,54 @@ func NewJobFromTask(task *models.Task) ([]*Job, error) {
|
||||
if server.Type == 0 {
|
||||
//密码验证登录服务器
|
||||
job := RemoteCommandJobByPassword(task.Id, server_id_int, task.TaskName, task.Command, server)
|
||||
job.task = task
|
||||
job.Concurrent = task.Concurrent == 1
|
||||
job.serverId = server_id_int
|
||||
job.serverName = server.ServerName
|
||||
job.Task = task
|
||||
job.Concurrent = false
|
||||
if task.Concurrent == 1 {
|
||||
job.Concurrent = true
|
||||
}
|
||||
//job.Concurrent = task.Concurrent == 1
|
||||
job.ServerId = server_id_int
|
||||
job.ServerName = server.ServerName
|
||||
jobArr = append(jobArr, job)
|
||||
} else {
|
||||
job := RemoteCommandJob(task.Id, server_id_int, task.TaskName, task.Command, server)
|
||||
job.task = task
|
||||
job.Concurrent = task.Concurrent == 1
|
||||
job.serverId = server_id_int
|
||||
job.serverName = server.ServerName
|
||||
job.Task = task
|
||||
job.Concurrent = false
|
||||
if task.Concurrent == 1 {
|
||||
job.Concurrent = true
|
||||
}
|
||||
//job.Concurrent = task.Concurrent == 1
|
||||
job.ServerId = server_id_int
|
||||
job.ServerName = server.ServerName
|
||||
jobArr = append(jobArr, job)
|
||||
}
|
||||
} else if server.ConnectionType == 1 {
|
||||
if server.Type == 0 {
|
||||
//密码验证登录服务器
|
||||
job := RemoteCommandJobByTelnetPassword(task.Id, server_id_int, task.TaskName, task.Command, server)
|
||||
job.task = task
|
||||
job.Concurrent = task.Concurrent == 1
|
||||
job.serverId = server_id_int
|
||||
job.serverName = server.ServerName
|
||||
job.Task = task
|
||||
job.Concurrent = false
|
||||
if task.Concurrent == 1 {
|
||||
job.Concurrent = true
|
||||
}
|
||||
//job.Concurrent = task.Concurrent == 1
|
||||
job.ServerId = server_id_int
|
||||
job.ServerName = server.ServerName
|
||||
jobArr = append(jobArr, job)
|
||||
}
|
||||
} else if server.ConnectionType == 2 {
|
||||
//密码验证登录服务器
|
||||
job := RemoteCommandJobByAgentPassword(task.Id, server_id_int, task.TaskName, task.Command, server)
|
||||
job.Task = task
|
||||
job.Concurrent = false
|
||||
if task.Concurrent == 1 {
|
||||
job.Concurrent = true
|
||||
}
|
||||
//job.Concurrent = task.Concurrent == 1
|
||||
job.ServerId = server_id_int
|
||||
job.ServerName = server.ServerName
|
||||
jobArr = append(jobArr, job)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,12 +172,12 @@ func NewJobFromTask(task *models.Task) ([]*Job, error) {
|
||||
|
||||
func NewCommandJob(id int, serverId int, name string, command string) *Job {
|
||||
job := &Job{
|
||||
id: id,
|
||||
name: name,
|
||||
Id: id,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
job.jobKey = jobKey(id, serverId)
|
||||
job.runFunc = func(timeout time.Duration) (string, string, error, bool) {
|
||||
job.JobKey = libs.JobKey(id, serverId)
|
||||
job.RunFunc = func(timeout time.Duration) (jobresult *JobResult) {
|
||||
bufOut := new(bytes.Buffer)
|
||||
bufErr := new(bytes.Buffer)
|
||||
//cmd := exec.Command("/bin/bash", "-c", command)
|
||||
@@ -130,8 +191,18 @@ func NewCommandJob(id int, serverId int, name string, command string) *Job {
|
||||
cmd.Stderr = bufErr
|
||||
cmd.Start()
|
||||
err, isTimeout := runCmdWithTimeout(cmd, timeout)
|
||||
jobresult = new(JobResult)
|
||||
jobresult.OutMsg = libs.GbkAsUtf8(bufOut.String())
|
||||
jobresult.ErrMsg = libs.GbkAsUtf8(bufErr.String())
|
||||
|
||||
return gbkAsUtf8(bufOut.String()), gbkAsUtf8(bufErr.String()), err, isTimeout
|
||||
jobresult.IsOk = true
|
||||
if err != nil {
|
||||
jobresult.IsOk = false
|
||||
}
|
||||
|
||||
jobresult.IsTimeout = isTimeout
|
||||
|
||||
return jobresult
|
||||
}
|
||||
return job
|
||||
}
|
||||
@@ -139,23 +210,29 @@ func NewCommandJob(id int, serverId int, name string, command string) *Job {
|
||||
//远程执行任务 密钥验证
|
||||
func RemoteCommandJob(id int, serverId int, name string, command string, servers *models.TaskServer) *Job {
|
||||
job := &Job{
|
||||
id: id,
|
||||
name: name,
|
||||
serverId: serverId,
|
||||
Id: id,
|
||||
Name: name,
|
||||
ServerId: serverId,
|
||||
}
|
||||
|
||||
job.jobKey = jobKey(id, serverId)
|
||||
job.JobKey = libs.JobKey(id, serverId)
|
||||
|
||||
job.runFunc = func(timeout time.Duration) (string, string, error, bool) {
|
||||
job.RunFunc = func(timeout time.Duration) (jobresult *JobResult) {
|
||||
jobresult = new(JobResult)
|
||||
jobresult.OutMsg = ""
|
||||
jobresult.ErrMsg = ""
|
||||
jobresult.IsTimeout = false
|
||||
|
||||
key, err := ioutil.ReadFile(servers.PrivateKeySrc)
|
||||
if err != nil {
|
||||
return "", "", err, false
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
// Create the Signer for this private key.
|
||||
signer, err := ssh.ParsePrivateKey(key)
|
||||
if err != nil {
|
||||
return "", "", err, false
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", servers.ServerIp, servers.Port)
|
||||
config := &ssh.ClientConfig{
|
||||
@@ -172,14 +249,16 @@ func RemoteCommandJob(id int, serverId int, name string, command string, servers
|
||||
// Connect to the remote server and perform the SSH handshake.47.93.220.5
|
||||
client, err := ssh.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
return "", "", err, false
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
|
||||
defer client.Close()
|
||||
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
return "", "", err, false
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
@@ -193,10 +272,14 @@ func RemoteCommandJob(id int, serverId int, name string, command string, servers
|
||||
|
||||
//session.Output(command)
|
||||
if err := session.Run(command); err != nil {
|
||||
return "", "", err, false
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
isTimeout := false
|
||||
return b.String(), c.String(), err, isTimeout
|
||||
jobresult.OutMsg = b.String()
|
||||
jobresult.ErrMsg = c.String()
|
||||
jobresult.IsOk = true
|
||||
jobresult.IsTimeout = false
|
||||
return
|
||||
}
|
||||
return job
|
||||
}
|
||||
@@ -212,12 +295,18 @@ func RemoteCommandJobByPassword(id int, serverId int, name string, command strin
|
||||
)
|
||||
|
||||
job := &Job{
|
||||
id: id,
|
||||
name: name,
|
||||
serverId: serverId,
|
||||
Id: id,
|
||||
Name: name,
|
||||
ServerId: serverId,
|
||||
ServerType: servers.ConnectionType,
|
||||
}
|
||||
job.jobKey = jobKey(id, serverId)
|
||||
job.runFunc = func(timeout time.Duration) (string, string, error, bool) {
|
||||
job.JobKey = libs.JobKey(id, serverId)
|
||||
job.RunFunc = func(timeout time.Duration) (jobresult *JobResult) {
|
||||
jobresult = new(JobResult)
|
||||
jobresult.OutMsg = ""
|
||||
jobresult.ErrMsg = ""
|
||||
jobresult.IsTimeout = false
|
||||
|
||||
// get auth method
|
||||
auth = make([]ssh.AuthMethod, 0)
|
||||
auth = append(auth, ssh.Password(servers.Password))
|
||||
@@ -235,14 +324,16 @@ func RemoteCommandJobByPassword(id int, serverId int, name string, command strin
|
||||
addr = fmt.Sprintf("%s:%d", servers.ServerIp, servers.Port)
|
||||
|
||||
if client, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
|
||||
return "", "", err, false
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
|
||||
defer client.Close()
|
||||
|
||||
// create session
|
||||
if session, err = client.NewSession(); err != nil {
|
||||
return "", "", err, false
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
@@ -251,10 +342,14 @@ func RemoteCommandJobByPassword(id int, serverId int, name string, command strin
|
||||
session.Stderr = &c
|
||||
//session.Output(command)
|
||||
if err := session.Run(command); err != nil {
|
||||
return "", "", err, false
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
isTimeout := false
|
||||
return b.String(), c.String(), err, isTimeout
|
||||
jobresult.OutMsg = b.String()
|
||||
jobresult.ErrMsg = c.String()
|
||||
jobresult.IsOk = true
|
||||
jobresult.IsTimeout = false
|
||||
return
|
||||
}
|
||||
|
||||
return job
|
||||
@@ -263,18 +358,23 @@ func RemoteCommandJobByPassword(id int, serverId int, name string, command strin
|
||||
func RemoteCommandJobByTelnetPassword(id int, serverId int, name string, command string, servers *models.TaskServer) *Job {
|
||||
|
||||
job := &Job{
|
||||
id: id,
|
||||
name: name,
|
||||
serverId: serverId,
|
||||
Id: id,
|
||||
Name: name,
|
||||
ServerId: serverId,
|
||||
}
|
||||
|
||||
job.jobKey = jobKey(id, serverId)
|
||||
job.runFunc = func(timeout time.Duration) (string, string, error, bool) {
|
||||
job.JobKey = libs.JobKey(id, serverId)
|
||||
job.RunFunc = func(timeout time.Duration) (jobresult *JobResult) {
|
||||
jobresult = new(JobResult)
|
||||
jobresult.OutMsg = ""
|
||||
jobresult.ErrMsg = ""
|
||||
jobresult.IsTimeout = false
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", servers.ServerIp, servers.Port)
|
||||
conn, err := gote.DialTimeout("tcp", addr, timeout)
|
||||
if err != nil {
|
||||
return "", "", err, false
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
@@ -282,28 +382,35 @@ func RemoteCommandJobByTelnetPassword(id int, serverId int, name string, command
|
||||
buf := make([]byte, 4096)
|
||||
|
||||
if _, err = conn.Read(buf); err != nil {
|
||||
return "", "", err, false
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = conn.Write([]byte(servers.ServerAccount + "\r\n")); err != nil {
|
||||
return "", "", err, false
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = conn.Read(buf); err != nil {
|
||||
return "", "", err, false
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = conn.Write([]byte(servers.Password + "\r\n")); err != nil {
|
||||
return "", "", err, false
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = conn.Read(buf); err != nil {
|
||||
return "", "", err, false
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
|
||||
loginStr := gbkAsUtf8(string(buf[:]))
|
||||
loginStr := libs.GbkAsUtf8(string(buf[:]))
|
||||
if !strings.Contains(loginStr, ">") {
|
||||
return "", "", errors.Errorf("Login failed!"), false
|
||||
jobresult.ErrMsg = jobresult.ErrMsg + "Login failed!"
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
|
||||
commandArr := strings.Split(command, "\n")
|
||||
@@ -312,44 +419,178 @@ func RemoteCommandJobByTelnetPassword(id int, serverId int, name string, command
|
||||
for _, c := range commandArr {
|
||||
_, err = conn.Write([]byte(c + "\r\n"))
|
||||
if err != nil {
|
||||
return "", "", err, false
|
||||
jobresult.IsOk = false
|
||||
return
|
||||
}
|
||||
|
||||
n, err = conn.Read(buf)
|
||||
|
||||
out = out + gbkAsUtf8(string(buf[0:n]))
|
||||
out = out + libs.GbkAsUtf8(string(buf[0:n]))
|
||||
if err != nil ||
|
||||
strings.Contains(out, "'"+c+"' is not recognized as an internal or external command") ||
|
||||
strings.Contains(out, "'"+c+"' 不是内部或外部命令,也不是可运行的程序") {
|
||||
return out, "", fmt.Errorf(gbkAsUtf8(string(buf[0:n]))), false
|
||||
jobresult.ErrMsg = jobresult.ErrMsg + " " + libs.GbkAsUtf8(string(buf[0:n]))
|
||||
jobresult.IsOk = false
|
||||
jobresult.OutMsg = out
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return out, "", nil, false
|
||||
jobresult.IsOk = true
|
||||
jobresult.OutMsg = out
|
||||
return
|
||||
}
|
||||
|
||||
return job
|
||||
}
|
||||
|
||||
func (j *Job) Status() int {
|
||||
return j.status
|
||||
func RemoteCommandJobByAgentPassword(id int, serverId int, name string, command string, servers *models.TaskServer) *Job {
|
||||
|
||||
job := &Job{
|
||||
Id: id,
|
||||
Name: name,
|
||||
ServerType: servers.ConnectionType,
|
||||
}
|
||||
|
||||
job.JobKey = libs.JobKey(id, serverId)
|
||||
job.RunFunc = func(timeout time.Duration) *JobResult {
|
||||
return new(JobResult)
|
||||
}
|
||||
return job
|
||||
|
||||
}
|
||||
|
||||
func (j *Job) GetStatus() int {
|
||||
return j.Status
|
||||
}
|
||||
|
||||
func (j *Job) GetName() string {
|
||||
return j.name
|
||||
return j.Name
|
||||
}
|
||||
|
||||
func (j *Job) GetId() int {
|
||||
return j.id
|
||||
return j.Id
|
||||
}
|
||||
|
||||
func (j *Job) GetLogId() int64 {
|
||||
return j.logId
|
||||
return j.LogId
|
||||
}
|
||||
|
||||
type RpcResult struct {
|
||||
Status int
|
||||
Message string
|
||||
}
|
||||
|
||||
func (j *Job) agentRun() (reply *JobResult) {
|
||||
|
||||
server, _ := models.TaskServerGetById(j.ServerId)
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", server.ServerIp, server.Port))
|
||||
reply = new(JobResult)
|
||||
if err != nil {
|
||||
logs.Error("Net error:", err)
|
||||
reply.IsOk = false
|
||||
reply.ErrMsg = "Net error:" + err.Error()
|
||||
reply.IsTimeout = false
|
||||
reply.OutMsg = ""
|
||||
return reply
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
|
||||
|
||||
defer client.Close()
|
||||
reply = new(JobResult)
|
||||
|
||||
task := j.Task
|
||||
err = client.Call("RpcTask.RunTask", task, &reply)
|
||||
if err != nil {
|
||||
reply.IsOk = false
|
||||
reply.ErrMsg = "Net error:" + err.Error()
|
||||
reply.IsTimeout = false
|
||||
reply.OutMsg = ""
|
||||
return reply
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestServer(server *models.TaskServer) error {
|
||||
if server.ConnectionType == 0 {
|
||||
switch server.Type {
|
||||
case 0:
|
||||
//密码登录
|
||||
return libs.RemoteCommandByPassword(server)
|
||||
case 1:
|
||||
//密钥登录
|
||||
return libs.RemoteCommandByKey(server)
|
||||
default:
|
||||
return errors.New("未知的登录方式")
|
||||
|
||||
}
|
||||
} else if server.ConnectionType == 1 {
|
||||
if server.Type == 0 {
|
||||
//密码登录]
|
||||
return libs.RemoteCommandByTelnetPassword(server)
|
||||
} else {
|
||||
return errors.New("Telnet方式暂不支持密钥登陆!")
|
||||
}
|
||||
|
||||
} else if server.ConnectionType == 2 {
|
||||
return libs.RemoteAgent(server)
|
||||
}
|
||||
|
||||
return errors.New("未知错误")
|
||||
}
|
||||
|
||||
func PollServer(j *Job) bool {
|
||||
//判断是否是当前执行器执行
|
||||
TaskServerIdsArr := strings.Split(j.Task.ServerIds, ",")
|
||||
num := len(TaskServerIdsArr)
|
||||
|
||||
if num == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
count := GetCounter(strconv.Itoa(j.Task.Id))
|
||||
index := count % num
|
||||
pollServerId, _ := strconv.Atoi(TaskServerIdsArr[index])
|
||||
|
||||
if j.ServerId != pollServerId {
|
||||
return false
|
||||
}
|
||||
|
||||
//本地服务器
|
||||
if pollServerId == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
//判断执行器或者服务器是否存活
|
||||
server, _ := models.TaskServerGetById(pollServerId)
|
||||
|
||||
if server.Status != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if err := TestServer(server); err != nil {
|
||||
server.Status = 1
|
||||
server.Update()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
func (j *Job) Run() {
|
||||
if !j.Concurrent && j.status > 0 {
|
||||
beego.Warn(fmt.Sprintf("任务[%d]上一次执行尚未结束,本次被忽略。", j.jobKey))
|
||||
//执行策略 轮询
|
||||
if j.Task.ServerType == 1 {
|
||||
if !PollServer(j) {
|
||||
return
|
||||
} else {
|
||||
SetCounter(strconv.Itoa(j.Task.Id))
|
||||
}
|
||||
}
|
||||
|
||||
if !j.Concurrent && j.Status > 0 {
|
||||
beego.Warn(fmt.Sprintf("任务[%d]上一次执行尚未结束,本次被忽略。", j.JobKey))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -366,42 +607,50 @@ func (j *Job) Run() {
|
||||
}()
|
||||
}
|
||||
|
||||
beego.Debug(fmt.Sprintf("开始执行任务: %d", j.jobKey))
|
||||
beego.Debug(fmt.Sprintf("开始执行任务: %d", j.JobKey))
|
||||
|
||||
j.status++
|
||||
j.Status++
|
||||
defer func() {
|
||||
j.status--
|
||||
j.Status--
|
||||
}()
|
||||
|
||||
t := time.Now()
|
||||
timeout := time.Duration(time.Hour * 24)
|
||||
if j.task.Timeout > 0 {
|
||||
timeout = time.Second * time.Duration(j.task.Timeout)
|
||||
if j.Task.Timeout > 0 {
|
||||
timeout = time.Second * time.Duration(j.Task.Timeout)
|
||||
}
|
||||
cmdOut, cmdErr, err, isTimeout := j.runFunc(timeout)
|
||||
|
||||
var jobResult = new(JobResult)
|
||||
//anget
|
||||
if j.ServerType == 2 {
|
||||
jobResult = j.agentRun()
|
||||
} else {
|
||||
jobResult = j.RunFunc(timeout)
|
||||
}
|
||||
|
||||
ut := time.Now().Sub(t) / time.Millisecond
|
||||
|
||||
// 插入日志
|
||||
log := new(models.TaskLog)
|
||||
log.TaskId = j.id
|
||||
log.ServerId = j.serverId
|
||||
log.ServerName = j.serverName
|
||||
log.Output = cmdOut
|
||||
log.Error = cmdErr
|
||||
log.TaskId = j.Id
|
||||
log.ServerId = j.ServerId
|
||||
log.ServerName = j.ServerName
|
||||
log.Output = jobResult.OutMsg
|
||||
log.Error = jobResult.ErrMsg
|
||||
log.ProcessTime = int(ut)
|
||||
log.CreateTime = t.Unix()
|
||||
|
||||
if isTimeout {
|
||||
if jobResult.IsTimeout {
|
||||
log.Status = models.TASK_TIMEOUT
|
||||
log.Error = fmt.Sprintf("任务执行超过 %d 秒\n----------------------\n%s\n", int(timeout/time.Second), cmdErr)
|
||||
} else if err != nil {
|
||||
log.Error = fmt.Sprintf("任务执行超过 %d 秒\n----------------------\n%s\n", int(timeout/time.Second), jobResult.ErrMsg)
|
||||
} else if !jobResult.IsOk {
|
||||
log.Status = models.TASK_ERROR
|
||||
log.Error = err.Error() + ":" + cmdErr
|
||||
log.Error = "ERROR:" + jobResult.ErrMsg
|
||||
}
|
||||
|
||||
if log.Status < 0 && j.task.IsNotify == 1 {
|
||||
if j.task.NotifyUserIds != "0" && j.task.NotifyUserIds != "" {
|
||||
adminInfo := AllAdminInfo(j.task.NotifyUserIds)
|
||||
if log.Status < 0 && j.Task.IsNotify == 1 {
|
||||
if j.Task.NotifyUserIds != "0" && j.Task.NotifyUserIds != "" {
|
||||
adminInfo := AllAdminInfo(j.Task.NotifyUserIds)
|
||||
phone := make(map[string]string, 0)
|
||||
dingtalk := make(map[string]string, 0)
|
||||
wechat := make(map[string]string, 0)
|
||||
@@ -432,9 +681,9 @@ func (j *Job) Run() {
|
||||
|
||||
title, content, taskOutput, errOutput := "", "", "", ""
|
||||
|
||||
notifyTpl, err := models.NotifyTplGetById(j.task.NotifyTplId)
|
||||
notifyTpl, err := models.NotifyTplGetById(j.Task.NotifyTplId)
|
||||
if err != nil {
|
||||
notifyTpl, err := models.NotifyTplGetByTplType(j.task.NotifyType, models.NotifyTplTypeSystem)
|
||||
notifyTpl, err := models.NotifyTplGetByTplType(j.Task.NotifyType, models.NotifyTplTypeSystem)
|
||||
if err == nil {
|
||||
title = notifyTpl.Title
|
||||
content = notifyTpl.Content
|
||||
@@ -450,10 +699,10 @@ func (j *Job) Run() {
|
||||
errOutput = strings.Replace(errOutput, "\"", "\\\"", -1)
|
||||
|
||||
if title != "" {
|
||||
title = strings.Replace(title, "{{TaskId}}", strconv.Itoa(j.task.Id), -1)
|
||||
title = strings.Replace(title, "{{ServerId}}", strconv.Itoa(j.serverId), -1)
|
||||
title = strings.Replace(title, "{{TaskName}}", j.task.TaskName, -1)
|
||||
title = strings.Replace(title, "{{ExecuteCommand}}", j.task.Command, -1)
|
||||
title = strings.Replace(title, "{{TaskId}}", strconv.Itoa(j.Task.Id), -1)
|
||||
title = strings.Replace(title, "{{ServerId}}", strconv.Itoa(j.ServerId), -1)
|
||||
title = strings.Replace(title, "{{TaskName}}", j.Task.TaskName, -1)
|
||||
title = strings.Replace(title, "{{ExecuteCommand}}", j.Task.Command, -1)
|
||||
title = strings.Replace(title, "{{ExecuteTime}}", beego.Date(time.Unix(log.CreateTime, 0), "Y-m-d H:i:s"), -1)
|
||||
title = strings.Replace(title, "{{ProcessTime}}", strconv.FormatFloat(float64(log.ProcessTime)/1000, 'f', 6, 64), -1)
|
||||
title = strings.Replace(title, "{{ExecuteStatus}}", TextStatus[status], -1)
|
||||
@@ -462,10 +711,10 @@ func (j *Job) Run() {
|
||||
}
|
||||
|
||||
if content != "" {
|
||||
content = strings.Replace(content, "{{TaskId}}", strconv.Itoa(j.task.Id), -1)
|
||||
content = strings.Replace(content, "{{ServerId}}", strconv.Itoa(j.serverId), -1)
|
||||
content = strings.Replace(content, "{{TaskName}}", j.task.TaskName, -1)
|
||||
content = strings.Replace(content, "{{ExecuteCommand}}", j.task.Command, -1)
|
||||
content = strings.Replace(content, "{{TaskId}}", strconv.Itoa(j.Task.Id), -1)
|
||||
content = strings.Replace(content, "{{ServerId}}", strconv.Itoa(j.ServerId), -1)
|
||||
content = strings.Replace(content, "{{TaskName}}", j.Task.TaskName, -1)
|
||||
content = strings.Replace(content, "{{ExecuteCommand}}", j.Task.Command, -1)
|
||||
content = strings.Replace(content, "{{ExecuteTime}}", beego.Date(time.Unix(log.CreateTime, 0), "Y-m-d H:i:s"), -1)
|
||||
content = strings.Replace(content, "{{ProcessTime}}", strconv.FormatFloat(float64(log.ProcessTime)/1000, 'f', 6, 64), -1)
|
||||
content = strings.Replace(content, "{{ExecuteStatus}}", TextStatus[status], -1)
|
||||
@@ -473,14 +722,14 @@ func (j *Job) Run() {
|
||||
content = strings.Replace(content, "{{ErrorOutput}}", errOutput, -1)
|
||||
}
|
||||
|
||||
if j.task.NotifyType == 0 && toEmail != "" {
|
||||
if j.Task.NotifyType == 0 && toEmail != "" {
|
||||
//邮件
|
||||
mailtype := "html"
|
||||
ok := notify.SendToChan(toEmail, title, content, mailtype)
|
||||
if !ok {
|
||||
fmt.Println("发送邮件错误", toEmail)
|
||||
}
|
||||
} else if j.task.NotifyType == 1 && len(phone) > 0 {
|
||||
} else if j.Task.NotifyType == 1 && len(phone) > 0 {
|
||||
//信息
|
||||
param := make(map[string]string)
|
||||
err := json.Unmarshal([]byte(content), ¶m)
|
||||
@@ -493,7 +742,7 @@ func (j *Job) Run() {
|
||||
if !ok {
|
||||
fmt.Println("发送信息错误", phone)
|
||||
}
|
||||
} else if j.task.NotifyType == 2 && len(dingtalk) > 0 {
|
||||
} else if j.Task.NotifyType == 2 && len(dingtalk) > 0 {
|
||||
//钉钉
|
||||
param := make(map[string]interface{})
|
||||
|
||||
@@ -507,7 +756,7 @@ func (j *Job) Run() {
|
||||
if !ok {
|
||||
fmt.Println("发送钉钉错误", dingtalk)
|
||||
}
|
||||
} else if j.task.NotifyType == 3 && len(wechat) > 0 {
|
||||
} else if j.Task.NotifyType == 3 && len(wechat) > 0 {
|
||||
//微信
|
||||
param := make(map[string]string)
|
||||
err := json.Unmarshal([]byte(content), ¶m)
|
||||
@@ -524,12 +773,12 @@ func (j *Job) Run() {
|
||||
}
|
||||
}
|
||||
|
||||
j.logId, _ = models.TaskLogAdd(log)
|
||||
j.LogId, _ = models.TaskLogAdd(log)
|
||||
|
||||
// 更新上次执行时间
|
||||
j.task.PrevTime = t.Unix()
|
||||
j.task.ExecuteTimes++
|
||||
j.task.Update("PrevTime", "ExecuteTimes")
|
||||
j.Task.PrevTime = t.Unix()
|
||||
j.Task.ExecuteTimes++
|
||||
j.Task.Update("PrevTime", "ExecuteTimes")
|
||||
}
|
||||
|
||||
//冗余代码
|
||||
@@ -572,16 +821,3 @@ func AllAdminInfo(adminIds string) []*adminInfo {
|
||||
|
||||
return adminInfos
|
||||
}
|
||||
|
||||
func gbkAsUtf8(str string) string {
|
||||
srcDecoder := mahonia.NewDecoder("gbk")
|
||||
desDecoder := mahonia.NewDecoder("utf-8")
|
||||
resStr := srcDecoder.ConvertString(str)
|
||||
_, resBytes, _ := desDecoder.Translate([]byte(resStr), true)
|
||||
return string(resBytes)
|
||||
}
|
||||
|
||||
//任务识别码
|
||||
func jobKey(taskId, serverId int) int {
|
||||
return taskId*10000 + serverId
|
||||
}
|
||||
|
||||
15
libs/convert.go
Normal file
15
libs/convert.go
Normal file
@@ -0,0 +1,15 @@
|
||||
/************************************************************
|
||||
** @Description: convert
|
||||
** @Author: george hao
|
||||
** @Date: 2019-06-28 09:34
|
||||
** @Last Modified by: george hao
|
||||
** @Last Modified time: 2019-06-28 09:34
|
||||
*************************************************************/
|
||||
package libs
|
||||
|
||||
import "fmt"
|
||||
|
||||
//查看数据类型
|
||||
func DataType(i interface{}) string {
|
||||
return fmt.Sprintf("%T", i)
|
||||
}
|
||||
@@ -9,10 +9,10 @@ package libs
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"net/http"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func HttpGet(url string, param map[string]string) (string, error) {
|
||||
|
||||
52
libs/ip.go
Normal file
52
libs/ip.go
Normal file
@@ -0,0 +1,52 @@
|
||||
/************************************************************
|
||||
** @Description: ip
|
||||
** @Author: george hao
|
||||
** @Date: 2019-06-27 09:20
|
||||
** @Last Modified by: george hao
|
||||
** @Last Modified time: 2019-06-27 09:20
|
||||
*************************************************************/
|
||||
package libs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func GetHostIp(IpType int) string {
|
||||
if IpType == 0 {
|
||||
return LocalIp()
|
||||
} else {
|
||||
return PublicIp()
|
||||
}
|
||||
}
|
||||
|
||||
func LocalIp() string {
|
||||
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, address := range addrs {
|
||||
// 检查ip地址判断是否回环地址
|
||||
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.IP.To4() != nil {
|
||||
return ipnet.IP.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func PublicIp() string {
|
||||
resp, err := http.Get("http://myexternalip.com/raw")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
content, _ := ioutil.ReadAll(resp.Body)
|
||||
//buf := new(bytes.Buffer)
|
||||
//buf.ReadFrom(resp.Body)
|
||||
//s := buf.String()
|
||||
return string(content)
|
||||
}
|
||||
150
libs/server.go
Normal file
150
libs/server.go
Normal file
@@ -0,0 +1,150 @@
|
||||
/************************************************************
|
||||
** @Description: server
|
||||
** @Author: george hao
|
||||
** @Date: 2019-07-02 11:16
|
||||
** @Last Modified by: george hao
|
||||
** @Last Modified time: 2019-07-02 11:16
|
||||
*************************************************************/
|
||||
package libs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/george518/PPGo_Job/common"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"github.com/linxiaozhi/go-telnet"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/rpc"
|
||||
"net/rpc/jsonrpc"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func RemoteCommandByTelnetPassword(servers *models.TaskServer) error {
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", servers.ServerIp, servers.Port)
|
||||
conn, err := gote.DialTimeout("tcp", addr, time.Second*10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
buf := make([]byte, 4096)
|
||||
_, err = conn.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Write([]byte(servers.ServerAccount + "\r\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Write([]byte(servers.Password + "\r\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
str := GbkAsUtf8(string(buf[:]))
|
||||
|
||||
if strings.Contains(str, ">") {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.Errorf("连接失败!")
|
||||
}
|
||||
|
||||
func RemoteCommandByPassword(servers *models.TaskServer) error {
|
||||
var (
|
||||
auth []ssh.AuthMethod
|
||||
addr string
|
||||
clientConfig *ssh.ClientConfig
|
||||
)
|
||||
|
||||
auth = make([]ssh.AuthMethod, 0)
|
||||
auth = append(auth, ssh.Password(servers.Password))
|
||||
|
||||
clientConfig = &ssh.ClientConfig{
|
||||
User: servers.ServerAccount,
|
||||
Auth: auth,
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
addr = fmt.Sprintf("%s:%d", servers.ServerIp, servers.Port)
|
||||
client, err := ssh.Dial("tcp", addr, clientConfig)
|
||||
if err == nil {
|
||||
defer client.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func RemoteCommandByKey(servers *models.TaskServer) error {
|
||||
key, err := ioutil.ReadFile(servers.PrivateKeySrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signer, err := ssh.ParsePrivateKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", servers.ServerIp, servers.Port)
|
||||
config := &ssh.ClientConfig{
|
||||
User: servers.ServerAccount,
|
||||
Auth: []ssh.AuthMethod{
|
||||
// Use the PublicKeys method for remote authentication.
|
||||
ssh.PublicKeys(signer),
|
||||
},
|
||||
//HostKeyCallback: ssh.FixedHostKey(hostKey),
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
client, err := ssh.Dial("tcp", addr, config)
|
||||
if err == nil {
|
||||
client.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func RemoteAgent(servers *models.TaskServer) error {
|
||||
|
||||
conn, err := net.Dial("tcp", servers.ServerIp+":"+strconv.Itoa(servers.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
|
||||
var reply *common.RpcResult
|
||||
defer client.Close()
|
||||
|
||||
ping := "ping"
|
||||
err = client.Call("RpcTask.HeartBeat", ping, &reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reply.Status == 200 {
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("链接错误:%v", reply.Message)
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ package libs
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"github.com/axgle/mahonia"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"time"
|
||||
@@ -67,3 +68,16 @@ func GetRandomString(lens int) string {
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
|
||||
func GbkAsUtf8(str string) string {
|
||||
srcDecoder := mahonia.NewDecoder("gbk")
|
||||
desDecoder := mahonia.NewDecoder("utf-8")
|
||||
resStr := srcDecoder.ConvertString(str)
|
||||
_, resBytes, _ := desDecoder.Translate([]byte(resStr), true)
|
||||
return string(resBytes)
|
||||
}
|
||||
|
||||
//任务识别码
|
||||
func JobKey(taskId, serverId int) int {
|
||||
return taskId*10000000 + serverId
|
||||
}
|
||||
|
||||
2
main.go
2
main.go
@@ -16,10 +16,12 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
//初始化数据模型
|
||||
var StartTime = time.Now().Unix()
|
||||
models.Init(StartTime)
|
||||
jobs.InitJobs()
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -10,8 +10,6 @@ package models
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/orm"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
@@ -31,7 +29,6 @@ func Init(startTime int64) {
|
||||
dbport = "3306"
|
||||
}
|
||||
dsn := dbuser + ":" + dbpassword + "@tcp(" + dbhost + ":" + dbport + ")/" + dbname + "?charset=utf8"
|
||||
fmt.Println(dsn)
|
||||
if timezone != "" {
|
||||
dsn = dsn + "&loc=" + url.QueryEscape(timezone)
|
||||
}
|
||||
|
||||
@@ -97,6 +97,20 @@ func TaskServerGetById(id int) (*TaskServer, error) {
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func TaskServerForActuator(serverIp string, port int) int {
|
||||
serverFilters := make([]interface{}, 0)
|
||||
serverFilters = append(serverFilters, "status__in", []int{0, 1})
|
||||
serverFilters = append(serverFilters, "server_ip", serverIp)
|
||||
serverFilters = append(serverFilters, "port", port)
|
||||
|
||||
server, _ := TaskServerGetList(1, 1, serverFilters...)
|
||||
|
||||
if len(server) == 1 {
|
||||
return server[0].Id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
//
|
||||
func TaskServerGetByIds(ids string) ([]*TaskServer, int64) {
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ type Task struct {
|
||||
Id int
|
||||
GroupId int
|
||||
ServerIds string
|
||||
ServerType int
|
||||
TaskName string
|
||||
Description string
|
||||
CronSpec string
|
||||
|
||||
@@ -332,11 +332,14 @@ COMMIT;
|
||||
|
||||
BEGIN;
|
||||
ALTER TABLE `ppgo_job2`.`pp_task` CHANGE COLUMN `server_id` `server_ids` varchar(200) NOT NULL DEFAULT '0' COMMENT '服务器id字符串,英文都好隔开';
|
||||
BEGIN;
|
||||
COMMIT;
|
||||
|
||||
BEGIN;
|
||||
ALTER TABLE `ppgo_job2`.`pp_task_log` ADD COLUMN `server_id` int(11) NOT NULL DEFAULT '-1' COMMENT '服务器ID,-1,异常' AFTER `task_id`, CHANGE COLUMN `output` `output` mediumtext NOT NULL COMMENT '任务输出' AFTER `server_id`, CHANGE COLUMN `error` `error` text NOT NULL COMMENT '错误信息' AFTER `output`, CHANGE COLUMN `status` `status` tinyint(4) NOT NULL COMMENT '状态' AFTER `error`, CHANGE COLUMN `process_time` `process_time` int(11) NOT NULL DEFAULT '0' COMMENT '消耗时间/毫秒' AFTER `status`, CHANGE COLUMN `create_time` `create_time` int(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间' AFTER `process_time`;
|
||||
ALTER TABLE `ppgo_job2`.`pp_task_log` ADD COLUMN `server_name` varchar(60) NOT NULL DEFAULT '\"\"' COMMENT '服务器名称' AFTER `server_id`, CHANGE COLUMN `output` `output` mediumtext NOT NULL COMMENT '任务输出' AFTER `server_name`, CHANGE COLUMN `error` `error` text NOT NULL COMMENT '错误信息' AFTER `output`, CHANGE COLUMN `status` `status` tinyint(4) NOT NULL COMMENT '状态' AFTER `error`, CHANGE COLUMN `process_time` `process_time` int(11) NOT NULL DEFAULT '0' COMMENT '消耗时间/毫秒' AFTER `status`, CHANGE COLUMN `create_time` `create_time` int(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间' AFTER `process_time`;
|
||||
BEGIN;
|
||||
COMMIT;
|
||||
|
||||
BEGIN;
|
||||
ALTER TABLE `ppgo_job2`.`pp_task` CHANGE COLUMN `is_notify` ` is_notify` tinyint(1) UNSIGNED NOT NULL DEFAULT '0' COMMENT '0-不通知,1-通知', ADD COLUMN `server_type` tinyint(1) UNSIGNED NOT NULL DEFAULT '1' COMMENT '执行策略:0-同时执行,1-轮询执行' AFTER `update_id`;
|
||||
COMMIT;
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
946
static/admin/js/formSelects-v3.js
Normal file
946
static/admin/js/formSelects-v3.js
Normal file
@@ -0,0 +1,946 @@
|
||||
'use strict';
|
||||
|
||||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
|
||||
|
||||
/**
|
||||
* name: formSelects
|
||||
* 基于Layui Select多选
|
||||
* version: 3.0.9.0607
|
||||
* https://faysunshine.com/layui/template/formSelects-v3/formSelects-v3.js
|
||||
*/
|
||||
(function (layui, window, factory) {
|
||||
if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object') {
|
||||
// 支持 CommonJS
|
||||
module.exports = factory();
|
||||
} else if (typeof define === 'function' && define.amd) {
|
||||
// 支持 AMD
|
||||
define(factory);
|
||||
} else if (window.layui && layui.define) {
|
||||
//layui加载
|
||||
layui.define(['jquery', 'form'], function (exports) {
|
||||
exports('formSelects', factory());
|
||||
});
|
||||
} else {
|
||||
window.formSelects = factory();
|
||||
}
|
||||
})(layui, window, function () {
|
||||
//针对IE的一些处理
|
||||
if (window.Map == undefined) {
|
||||
var _Map = function _Map() {
|
||||
this.value = {};
|
||||
};
|
||||
|
||||
_Map.prototype.set = function (key, val) {
|
||||
this.value[key] = val;
|
||||
};
|
||||
|
||||
_Map.prototype.get = function (key) {
|
||||
return this.value[key];
|
||||
};
|
||||
|
||||
_Map.prototype.has = function (key) {
|
||||
return this.value.hasOwnProperty(key);
|
||||
};
|
||||
|
||||
_Map.prototype.delete = function (key) {
|
||||
delete this.value[key];
|
||||
};
|
||||
|
||||
window.Map = _Map;
|
||||
}
|
||||
|
||||
var $ = layui.jquery || $,
|
||||
form = layui.form,
|
||||
select3 = {
|
||||
value: function value(name) {
|
||||
var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'all';
|
||||
var vals = arguments[2];
|
||||
|
||||
if (Array.isArray(type)) {
|
||||
vals = type;
|
||||
type = 'all';
|
||||
}
|
||||
if (name && vals && Array.isArray(vals)) {
|
||||
var options = commons.data.confs.get(name);
|
||||
if (options) {
|
||||
var dl = commons.methods.getDiv(options).find('dl');
|
||||
if (!options.repeat) {
|
||||
vals = new Set(vals);
|
||||
}
|
||||
var on = options.on;
|
||||
options.on = null;
|
||||
commons.methods.removeAll(options);
|
||||
options.delete = false;
|
||||
vals.forEach(function (val) {
|
||||
dl.find('dd:not(.layui-disabled)[lay-value=\'' + val + '\']').click();
|
||||
});
|
||||
options.on = on;
|
||||
}
|
||||
return;
|
||||
}
|
||||
var arr = commons.data.values.get(name);
|
||||
if (!arr) {
|
||||
return vals;
|
||||
}
|
||||
if (type == 'val') {
|
||||
return arr.map(function (val) {
|
||||
return val.val;
|
||||
});
|
||||
}
|
||||
if (type == 'valStr') {
|
||||
return arr.map(function (val) {
|
||||
return val.val;
|
||||
}).join(',');
|
||||
}
|
||||
if (type == 'name') {
|
||||
return arr.map(function (val) {
|
||||
return val.name;
|
||||
});
|
||||
}
|
||||
if (type == 'nameStr') {
|
||||
return arr.map(function (val) {
|
||||
return val.name;
|
||||
}).join(',');
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
render: function render(options) {
|
||||
if (options) {
|
||||
if (commons.data.confs.get(options.name)) {
|
||||
options = commons.methods.cloneOptions(options, commons.data.confs.get(options.name));
|
||||
commons.methods.init(options);
|
||||
} else {
|
||||
var dom = commons.methods.getDom(options, true);
|
||||
if (dom.length) {
|
||||
var hisOptions = commons.methods.cloneOptions(commons.methods.getOptions(dom), commons.data.DEFAULT_OPTIONS);
|
||||
options = commons.methods.cloneOptions(options, hisOptions);
|
||||
commons.methods.init(options);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
commons.methods.autoInit();
|
||||
}
|
||||
},
|
||||
delete: function _delete(name, abs) {
|
||||
if (name && commons.data.confs.get(name)) {
|
||||
var dom = commons.methods.getDom({
|
||||
name: name
|
||||
});
|
||||
if (dom.parent().hasClass(commons.data.pclass)) {
|
||||
if (abs) {
|
||||
dom.removeAttr(commons.data.name);
|
||||
}
|
||||
dom.removeAttr('style');
|
||||
dom.parent()[0].outerHTML = dom[0].outerHTML;
|
||||
}
|
||||
commons.data.confs.delete(name);
|
||||
for (var item in commons.data.temps) {
|
||||
commons.data.temps[item].delete(name);
|
||||
}
|
||||
commons.data.values.delete(name);
|
||||
}
|
||||
},
|
||||
style: function style(name, colors) {
|
||||
if (name) {
|
||||
if (!colors) {
|
||||
commons.methods.loadCss(name, null);
|
||||
} else if (Array.isArray(colors)) {
|
||||
commons.methods.loadCss(name, colors);
|
||||
} else {
|
||||
var arr = [colors.labelBgColor, colors.labelColor, colors.labelIconBgColor, colors.labelIconColor, colors.labelLabelBorderColor, colors.thisBgColor, colors.thisColor];
|
||||
commons.methods.loadCss(name, arr);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
commons = {
|
||||
data: {
|
||||
name: 'xm-select',
|
||||
pclass: 'xm-select-parent',
|
||||
vclass: 'xm-select-validate',
|
||||
DEFAULT_OPTIONS: {
|
||||
name: null, //xm-select="xxx"
|
||||
type: 1, //显示模式, 1:layui-this, 2:checkbox, 3:icon
|
||||
icon: {
|
||||
class: 'layui-icon-ok',
|
||||
text: ''
|
||||
},
|
||||
max: null,
|
||||
maxTips: null,
|
||||
init: null, //初始化的选择值,
|
||||
on: null, //select值发生变化
|
||||
data: null
|
||||
},
|
||||
DEFAULT_RENDER: {
|
||||
arr: null,
|
||||
name: 'name',
|
||||
val: 'val',
|
||||
selected: 'selected',
|
||||
disabled: 'disabled'
|
||||
},
|
||||
confs: new Map(),
|
||||
temps: {
|
||||
dom: new Map(),
|
||||
div: new Map()
|
||||
},
|
||||
resize: new Map(),
|
||||
values: new Map(),
|
||||
times: new Map()
|
||||
},
|
||||
methods: {
|
||||
init: function init(options, clone) {
|
||||
if (!clone) {
|
||||
options = commons.methods.cloneOptions(options);
|
||||
}
|
||||
//原始dom添加一个filter
|
||||
var _ref = ['xm-' + options.name, commons.methods.getDom(options, true)],
|
||||
filter = _ref[0],
|
||||
dom = _ref[1];
|
||||
|
||||
if (dom.next().hasClass('layui-form-select')) {
|
||||
dom.next().remove();
|
||||
}
|
||||
if (options.data && options.data.arr) {
|
||||
var os = $.extend({}, commons.data.DEFAULT_RENDER, options.data);
|
||||
var html = '<option value=""></option>';
|
||||
for (var i in os.arr) {
|
||||
var db = os.arr[i];
|
||||
if (db.arr && Array.isArray(db.arr)) {
|
||||
html += '<optgroup label="' + db.name + '">';
|
||||
for (var j in db.arr) {
|
||||
var gdb = db.arr[j];
|
||||
html += '<option value="' + gdb[os.val] + '" ' + (gdb[os.selected] ? 'selected="selected"' : '') + ' ' + (gdb[os.disabled] ? 'disabled="disabled"' : '') + '>' + gdb[os.name] + '</option>';
|
||||
}
|
||||
html += '</optgroup>';
|
||||
} else {
|
||||
html += '<option value="' + db[os.val] + '" ' + (db[os.selected] ? 'selected="selected"' : '') + ' ' + (db[os.disabled] ? 'disabled="disabled"' : '') + '>' + db[os.name] + '</option>';
|
||||
}
|
||||
}
|
||||
dom.html(html);
|
||||
}
|
||||
//判断dom中是否包含了空的option, 如果不包含, 添加
|
||||
if (!dom.find('option[value=""]').length) {
|
||||
$('<option value=""></option>').insertBefore(dom.find('option:first'));
|
||||
}
|
||||
if (dom.parent().hasClass(commons.data.pclass)) {
|
||||
dom.parent().attr('lay-filter', filter).addClass('layui-form');
|
||||
} else {
|
||||
dom.wrap('<div class="layui-form ' + commons.data.pclass + '" lay-filter="' + filter + '"></div>');
|
||||
}
|
||||
dom.attr('lay-filter', filter);
|
||||
options.type ? dom.attr('xm-select-type', options.type) : dom.removeAttr('xm-select-type');
|
||||
options.max ? dom.attr('xm-select-max', options.max) : dom.removeAttr('xm-select-max');
|
||||
commons.methods.formRender('select', filter, true);
|
||||
//1.去掉layui的原始渲染
|
||||
commons.methods.getDom(options).next().addClass(commons.data.vclass);
|
||||
//2.
|
||||
commons.data.confs.set(options.name, options);
|
||||
for (var item in commons.data.temps) {
|
||||
commons.data.temps[item].delete(options.name);
|
||||
}
|
||||
commons.data.values.delete(options.name);
|
||||
//3.渲染input
|
||||
commons.methods.overrideInput(options);
|
||||
commons.methods.typeInit(options, filter);
|
||||
//4.初始化init
|
||||
var vals = commons.methods.getInitVal(options);
|
||||
vals.forEach(function (val) {
|
||||
commons.methods.valHandler(options, val, true);
|
||||
});
|
||||
commons.methods.showPlaceholder(options);
|
||||
commons.methods.retop(options);
|
||||
commons.methods.removeDefaultClass(options);
|
||||
commons.methods.on(options, filter);
|
||||
},
|
||||
addLabel: function addLabel(options, vals) {
|
||||
var ipt = commons.methods.getIpt(options);
|
||||
ipt.find('.xm-select-empty').remove();
|
||||
vals.forEach(function (val) {
|
||||
var tips = 'fsw="' + options.name + '"';
|
||||
var _ref2 = [$('<span ' + tips + ' value=\'' + val.val + '\'><font ' + tips + '>' + val.name + '</font></span>'), $('<i ' + tips + ' class="layui-icon">ဆ</i>')],
|
||||
$label = _ref2[0],
|
||||
$close = _ref2[1];
|
||||
|
||||
$label.append($close);
|
||||
ipt.append($label);
|
||||
});
|
||||
},
|
||||
delLabel: function delLabel(options, vals) {
|
||||
var ipt = commons.methods.getIpt(options);
|
||||
vals.forEach(function (val) {
|
||||
ipt.find('span[value=\'' + val.val + '\']:first').remove();
|
||||
});
|
||||
},
|
||||
showPlaceholder: function showPlaceholder(options) {
|
||||
var vals = commons.methods.getValues(options);
|
||||
if (!vals.length) {
|
||||
var _ref3 = ['fsw="' + options.name + '"', commons.methods.getIpt(options)],
|
||||
tips = _ref3[0],
|
||||
ipt = _ref3[1];
|
||||
|
||||
if (!ipt.find('.xm-select-empty').length) {
|
||||
var _tips = options.tips ? options.tips : ipt.prev().attr('placeholder');
|
||||
var cls = 'fsw="' + options.name + '"';
|
||||
var span = $('<span ' + cls + ' class="xm-select-empty">' + _tips + '</span>');
|
||||
ipt.append(span);
|
||||
}
|
||||
}
|
||||
},
|
||||
valHandler: function valHandler(options, val, isAdd, isShow) {
|
||||
var vals = commons.methods.getValues(options);
|
||||
var dd = commons.methods.getDiv(options).find('dl dd[lay-value=\'' + val.val + '\']');
|
||||
if (isAdd) {
|
||||
if (!options.max || options.max && vals.length < options.max) {
|
||||
vals.push(val);
|
||||
commons.methods.addLabel(options, [val]);
|
||||
commons.methods.typeHandler(options, dd, isAdd);
|
||||
} else {
|
||||
commons.methods.maxTips(options, val);
|
||||
commons.methods.typeHandler(options, dd, false);
|
||||
}
|
||||
} else {
|
||||
if (!dd.hasClass('layui-disabled')) {
|
||||
commons.methods.remove(vals, val);
|
||||
commons.methods.delLabel(options, [val]);
|
||||
if (commons.methods.indexOf(vals, val) == -1) {
|
||||
commons.methods.typeHandler(options, dd, isAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
commons.methods.retop(options);
|
||||
},
|
||||
typeInit: function typeInit(options, filter) {
|
||||
var div = commons.methods.getDiv(options);
|
||||
if (options.type == 2) {
|
||||
//checkbox
|
||||
div.find('dl dd:not(.layui-select-tips)').each(function (index, target) {
|
||||
var $target = $(target);
|
||||
var text = $target.text();
|
||||
var dis = $target.hasClass('layui-disabled') ? 'disabled' : '';
|
||||
$target.text('');
|
||||
$target.append('\n\t\t\t\t\t\t\t\t<span lay-filter="' + filter + '">\n\t\t\t\t\t\t\t\t\t<input type="checkbox" title="' + text + '" lay-skin="primary" ' + dis + '> \t\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t');
|
||||
});
|
||||
form.render('checkbox', filter);
|
||||
div.find('dl dd .layui-form-checkbox').css('margin-top', '1px');
|
||||
} else {
|
||||
div.find('dl dd:not(.layui-select-tips)').each(function (index, target) {
|
||||
$(target).css({
|
||||
margin: '1px 0'
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
typeHandler: function typeHandler(options, dd, isAdd) {
|
||||
if (options.type == 3) {
|
||||
//对勾
|
||||
if (isAdd) {
|
||||
var span = $('\n\t\t\t\t\t\t\t\t<span><i class="layui-icon ' + options.icon.class + '">' + options.icon.text + '</i></span>\t\n\t\t\t\t\t\t\t');
|
||||
dd.append(span);
|
||||
} else {
|
||||
dd.find('span').remove();
|
||||
}
|
||||
} else if (options.type == 2) {
|
||||
//checkbox
|
||||
if (isAdd) {
|
||||
dd.find('.layui-form-checkbox').addClass('layui-form-checked');
|
||||
} else {
|
||||
dd.find('.layui-form-checkbox').removeClass('layui-form-checked');
|
||||
}
|
||||
}
|
||||
isAdd ? dd.addClass(commons.data.name + '-this') : dd.removeClass(commons.data.name + '-this');
|
||||
dd.removeClass('layui-this');
|
||||
commons.methods.showPlaceholder(options);
|
||||
},
|
||||
on: function on(options, filter) {
|
||||
form.on('select(' + filter + ')', function (data) {
|
||||
if (options.radio) {
|
||||
commons.methods.getDiv(options).find('dl').removeClass('xm-select-show').addClass('xm-select-hidn').css('display', 'none');
|
||||
var selected = void 0,
|
||||
val = void 0;
|
||||
if (data.value) {
|
||||
val = {
|
||||
name: commons.methods.getDom(options).find('option[value=\'' + data.value + '\']').text(),
|
||||
val: data.value
|
||||
};
|
||||
selected = commons.methods.indexOf(commons.methods.getValues(options), val) == -1;
|
||||
if (selected) {
|
||||
var hisVal = select3.value(options.name)[0];
|
||||
if (hisVal) {
|
||||
commons.methods.valHandler(options, hisVal, false, false);
|
||||
commons.methods.setValues(options, []);
|
||||
}
|
||||
}
|
||||
commons.methods.valHandler(options, val, selected, false);
|
||||
} else {
|
||||
selected = false;
|
||||
val = select3.value(options.name)[0];
|
||||
if (val) {
|
||||
commons.methods.valHandler(options, val, selected, false);
|
||||
commons.methods.setValues(options, []);
|
||||
}
|
||||
}
|
||||
if (val && options.on && options.on instanceof Function) {
|
||||
options.on(data, select3.value(options.name), val, selected);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (commons.methods.getDiv(options).hasClass('layui-form-selectup') || commons.methods.getDiv(options).find('dl').css('top').indexOf('-') == 0) {
|
||||
setTimeout(function () {
|
||||
commons.methods.getDiv(options).addClass('layui-form-selectup');
|
||||
}, 50);
|
||||
}
|
||||
|
||||
if (options.repeat) {
|
||||
if (data.value) {
|
||||
var ipt = commons.methods.getIpt(options);
|
||||
if (options.delete) {
|
||||
var arr = select3.value(options.name);
|
||||
var _val = arr[commons.methods.indexOf(arr, data.value)];
|
||||
if (_val) {
|
||||
commons.methods.valHandler(options, _val, false);
|
||||
ipt.parent().next().find('dd[lay-value=\'' + data.value + '\'] .layui-form-checkbox > i').html('');
|
||||
if (options.on && options.on instanceof Function) {
|
||||
options.on(data, select3.value(options.name), _val, false);
|
||||
}
|
||||
}
|
||||
options.delete = false;
|
||||
return;
|
||||
} else {
|
||||
var vals = commons.methods.getValues(options);
|
||||
var _val2 = {
|
||||
name: commons.methods.getDom(options).find('option[value=\'' + data.value + '\']').text(),
|
||||
val: data.value
|
||||
};
|
||||
commons.methods.valHandler(options, _val2, true);
|
||||
if (options.on && options.on instanceof Function) {
|
||||
options.on(data, select3.value(options.name), _val2, true);
|
||||
}
|
||||
}
|
||||
setTimeout(function () {
|
||||
commons.methods.getDiv(options).addClass('layui-form-selected').find('dl dd.layui-this').removeClass('layui-this');
|
||||
}, 3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (data.value) {
|
||||
var _val3 = {
|
||||
name: commons.methods.getDom(options).find('option[value=\'' + data.value + '\']').text(),
|
||||
val: data.value
|
||||
};
|
||||
var _selected = commons.methods.indexOf(commons.methods.getValues(options), _val3) == -1;
|
||||
commons.methods.valHandler(options, _val3, _selected, true);
|
||||
|
||||
if (options.on && options.on instanceof Function) {
|
||||
options.on(data, select3.value(options.name), _val3, _selected);
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
commons.methods.getDiv(options).addClass('layui-form-selected').find('dl dd.layui-this').removeClass('layui-this');
|
||||
}, 3);
|
||||
} else {
|
||||
if (commons.methods.getDom(options).attr('xm-select-search') == undefined) {
|
||||
commons.methods.removeAll(options);
|
||||
setTimeout(function () {
|
||||
commons.methods.getDiv(options).addClass('layui-form-selected');
|
||||
}, 3);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
maxTips: function maxTips(options, val) {
|
||||
if (options.maxTips && options.maxTips instanceof Function) {
|
||||
options.maxTips(select3.value(options.name), val, options.max);
|
||||
return;
|
||||
}
|
||||
var ipt = commons.methods.getIpt(options);
|
||||
if (ipt.parents('.layui-form-item[pane]').length) {
|
||||
ipt = ipt.parents('.layui-form-item[pane]');
|
||||
}
|
||||
ipt.css('border-color', 'red');
|
||||
setTimeout(function () {
|
||||
ipt.css('border-color', '#e6e6e6');
|
||||
}, 300);
|
||||
},
|
||||
indexOf: function indexOf(arr, val) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
if (arr[i].val == val || arr[i].val == (val ? val.val : val) || arr[i] == val || JSON.stringify(arr[i]) == JSON.stringify(val)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
remove: function remove(arr, val) {
|
||||
var index = commons.methods.indexOf(arr, val ? val.val : val);
|
||||
if (index > -1) {
|
||||
arr.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
removeAll: function removeAll(options) {
|
||||
var div = commons.methods.getDiv(options);
|
||||
var vals = div.find('.xm-select > span');
|
||||
vals.each(function (index, item) {
|
||||
options.delete = true;
|
||||
div.find('dl dd.xm-select-this:not(.layui-disabled)[lay-value=\'' + item.getAttribute('value') + '\']').click();
|
||||
});
|
||||
return vals.length;
|
||||
},
|
||||
removeDefaultClass: function removeDefaultClass(options) {
|
||||
var _ref4 = [commons.methods.getDom(options), commons.methods.getDiv(options)],
|
||||
dom = _ref4[0],
|
||||
div = _ref4[1];
|
||||
|
||||
div.find('dl').addClass('xm-select-hidn');
|
||||
div.find('dl dd.layui-this').removeClass('layui-this');
|
||||
div.find('dl').append('<dt style="display:none;">x<dt/>');
|
||||
var text = options.radio ? '请选择' : '清空已选择' + (options.max ? '. \u5F53\u524D\u914D\u7F6E: \u6700\u591A\u9009\u62E9 ' + options.max + ' \u4E2A' : '');
|
||||
var html = '<span>' + text + '</span>';
|
||||
var that = div.find('dl dd.layui-select-tips');
|
||||
if (dom.attr('xm-select-search') != undefined) {
|
||||
var icon = '';
|
||||
html = '<input class="layui-input xm-select-input-search" placeholder="\u5173\u952E\u5B57\u641C\u7D22"><span><i title="' + text + '" xm-select-clear="' + options.name + '" class="layui-icon">' + icon + '</i></span>';
|
||||
that.addClass('xm-select-dd-search');
|
||||
that.off('click');
|
||||
}
|
||||
commons.methods.handlerDataTable(dom, '38px');
|
||||
that.html(html);
|
||||
},
|
||||
handlerDataTable: function handlerDataTable(dom, height) {
|
||||
if (dom.length == undefined) {
|
||||
dom = $(dom);
|
||||
}
|
||||
if (dom.parents('div.layui-table-cell').length) {
|
||||
var index = dom.parents('tr').attr('data-index');
|
||||
dom.parents('.layui-table-box').find('tbody tr[data-index=\'' + index + '\'] div.layui-table-cell').css({
|
||||
height: height,
|
||||
lineHeight: height,
|
||||
overflow: 'unset'
|
||||
});
|
||||
}
|
||||
},
|
||||
overrideInput: function overrideInput(options) {
|
||||
var _ref5 = [commons.methods.getDiv(options), commons.methods.getDom(options)],
|
||||
_div = _ref5[0],
|
||||
_dom = _ref5[1];
|
||||
var _ref6 = [_div.find('.layui-select-title input:first'), $('<div class="layui-input ' + commons.data.name + '"></div>')],
|
||||
$input = _ref6[0],
|
||||
$orinput = _ref6[1];
|
||||
|
||||
$orinput.insertAfter($input);
|
||||
if ($input.parents('.layui-form-pane').length && $input.parents('.layui-form-item[pane]').length) {
|
||||
$orinput.css('border', 'none');
|
||||
}
|
||||
if (options.height) {
|
||||
$orinput.addClass('.xm-select-height').css('height', options.height);
|
||||
}
|
||||
if (_dom.attr('disabled') != undefined) {
|
||||
$orinput.append($('<div style="\n\t\t\t\t\t\t\t width: 100%;\n\t\t\t\t\t\t\t height: 100%;\n\t\t\t\t\t\t\t display: block;\n\t\t\t\t\t\t\t position: absolute;\n\t\t\t\t\t\t\t left: 0;\n\t\t\t\t\t\t\t top: 0;\n\t\t\t\t\t\t\t"></div>').addClass('layui-disabled'));
|
||||
}
|
||||
},
|
||||
retop: function retop(options) {
|
||||
var div = commons.methods.getIpt(options);
|
||||
if (div.length) {
|
||||
var dl = div.parent('.layui-select-title').next();
|
||||
var up = dl.parent().hasClass('layui-form-selectup') || dl.css('top').indexOf('-') == 0;
|
||||
var time = commons.data.times.get(options.name);
|
||||
if (time) {
|
||||
if (Date.now() - time.time < 100 || options.delete) {
|
||||
up = time.up;
|
||||
options.delete = false;
|
||||
} else {
|
||||
time.up = up;
|
||||
}
|
||||
time.time = Date.now();
|
||||
} else {
|
||||
commons.data.times.set(options.name, {
|
||||
time: Date.now(),
|
||||
up: up
|
||||
});
|
||||
}
|
||||
if (up) {
|
||||
div.parent('.layui-select-title').next().css({
|
||||
top: 'auto',
|
||||
bottom: div[0].offsetTop + div.height() + 14 + 'px'
|
||||
});
|
||||
} else {
|
||||
div.parent('.layui-select-title').next().css({
|
||||
top: div[0].offsetTop + div.height() + 14 + 'px',
|
||||
bottom: 'auto'
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
getElementTop: function getElementTop(element) {
|
||||
var actualTop = element.offsetTop;
|
||||
var current = element.offsetParent;
|
||||
while (current !== null) {
|
||||
actualTop += current.offsetTop;
|
||||
current = current.offsetParent;
|
||||
}
|
||||
return actualTop;
|
||||
},
|
||||
getDom: function getDom(options, isNew) {
|
||||
var _ref7 = [commons.data.temps.dom, options.name],
|
||||
_dom = _ref7[0],
|
||||
_name = _ref7[1];
|
||||
|
||||
if (isNew) {
|
||||
_dom.set(_name, $('select[' + commons.data.name + '=\'' + _name + '\']'));
|
||||
}
|
||||
if (!_dom.has(_name)) {
|
||||
_dom.set(_name, $('select[' + commons.data.name + '=\'' + _name + '\']'));
|
||||
}
|
||||
return _dom.get(_name);
|
||||
},
|
||||
getDiv: function getDiv(options) {
|
||||
var _ref8 = [commons.data.temps.div, options.name],
|
||||
_div = _ref8[0],
|
||||
_name = _ref8[1];
|
||||
|
||||
if (!_div.has(_name)) {
|
||||
_div.set(_name, $('select[' + commons.data.name + '=\'' + _name + '\']').next());
|
||||
}
|
||||
return _div.get(_name);
|
||||
},
|
||||
getIpt: function getIpt(options) {
|
||||
return commons.methods.getDiv(options).find('div .' + commons.data.name);
|
||||
},
|
||||
getInitVal: function getInitVal(options) {
|
||||
var _dom = commons.methods.getDom(options);
|
||||
var vals = options.init ? options.init : _dom.find('option[selected]').map(function (index, target) {
|
||||
return $(target).attr('value');
|
||||
}).toArray();
|
||||
var result = void 0;
|
||||
if (options.radio) {
|
||||
var one = vals.map(function (val) {
|
||||
var opt = _dom.find('option[value=\'' + val + '\']');
|
||||
return {
|
||||
name: opt.attr('disabled') != undefined ? null : opt.text(),
|
||||
val: val + ""
|
||||
};
|
||||
}).filter(function (val) {
|
||||
return val.name && val.val;
|
||||
})[0];
|
||||
result = one ? [one] : [];
|
||||
} else {
|
||||
result = vals.map(function (val) {
|
||||
return {
|
||||
name: _dom.find('option[value=\'' + val + '\']').text(),
|
||||
val: val + ""
|
||||
};
|
||||
}).filter(function (val) {
|
||||
return val.name && val.val;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
},
|
||||
getValues: function getValues(options) {
|
||||
var _ref9 = [commons.data.values, options.name],
|
||||
_arr = _ref9[0],
|
||||
_name = _ref9[1];
|
||||
|
||||
if (!_arr.has(_name)) {
|
||||
_arr.set(_name, []);
|
||||
}
|
||||
return _arr.get(_name);
|
||||
},
|
||||
setValues: function setValues(options, vals) {
|
||||
var _ref10 = [commons.data.values, options.name],
|
||||
_arr = _ref10[0],
|
||||
_name = _ref10[1];
|
||||
|
||||
_arr.set(_name, vals);
|
||||
},
|
||||
getOptions: function getOptions(sel) {
|
||||
return {
|
||||
name: sel.attr('' + commons.data.name),
|
||||
type: sel.attr(commons.data.name + '-type'),
|
||||
max: sel.attr(commons.data.name + '-max'),
|
||||
icon: sel.attr(commons.data.name + '-icon'),
|
||||
tips: sel.attr(commons.data.name + '-placeholder'),
|
||||
height: sel.attr(commons.data.name + '-height'),
|
||||
radio: sel.attr(commons.data.name + '-radio') != undefined,
|
||||
repeat: sel.attr(commons.data.name + '-repeat') != undefined
|
||||
};
|
||||
},
|
||||
cloneOptions: function cloneOptions(options, hisOptions) {
|
||||
if (!hisOptions) {
|
||||
hisOptions = commons.data.DEFAULT_OPTIONS;
|
||||
}
|
||||
if (options.icon && typeof options.icon == 'string') {
|
||||
var icon = options.icon;
|
||||
if (icon.indexOf('layui') == 0) {
|
||||
options.icon = {
|
||||
class: icon,
|
||||
text: ''
|
||||
};
|
||||
} else {
|
||||
options.icon = {
|
||||
class: '',
|
||||
text: icon
|
||||
};
|
||||
}
|
||||
} else {
|
||||
var v = layui.v.split('.');
|
||||
if (v[0] < 2 || v[1] <= 2) {
|
||||
options.icon = {
|
||||
class: '',
|
||||
text: hisOptions.icon.text
|
||||
};
|
||||
} else {
|
||||
options.icon = {
|
||||
class: hisOptions.icon.class,
|
||||
text: ''
|
||||
};
|
||||
}
|
||||
}
|
||||
return $.extend({}, hisOptions, options);
|
||||
},
|
||||
autoInit: function autoInit(repeat) {
|
||||
$('select[' + commons.data.name + ']').each(function (index, target) {
|
||||
var sel = $(target);
|
||||
sel.css('display', 'none');
|
||||
var options = commons.methods.getOptions(sel);
|
||||
if (!repeat) {
|
||||
var hisOptions = commons.data.confs.get(options.name);
|
||||
options.init = select3.value(options.name, 'val');
|
||||
commons.methods.init(commons.methods.cloneOptions(options, hisOptions), true);
|
||||
} else {
|
||||
commons.methods.init(options);
|
||||
}
|
||||
});
|
||||
},
|
||||
listenCloseHandler: function listenCloseHandler(e, that, block) {
|
||||
if (block.length) {
|
||||
var _ref11 = [block.find('.layui-form-select'), block.find('dl')],
|
||||
div = _ref11[0],
|
||||
dl = _ref11[1];
|
||||
|
||||
var name = block.find('select').attr('xm-select');
|
||||
|
||||
if (that.hasClass('layui-disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (that.attr('xm-select-clear') != undefined) {
|
||||
if (that.is('i')) {
|
||||
var options = commons.data.confs.get($(e.target).attr('xm-select-clear'));
|
||||
if (options) {
|
||||
var length = commons.methods.removeAll(options);
|
||||
if (options.radio && length) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
div.addClass('layui-form-selected');
|
||||
dl.find('.xm-select-input-search').focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (that.hasClass('xm-select-dd-search') || that.hasClass('xm-select-input-search')) {
|
||||
div.addClass('layui-form-selected');
|
||||
dl.find('.xm-select-input-search').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
$('.xm-select-parent > select:not([xm-select=\'' + name + '\']) + div > dl').removeClass('xm-select-show').addClass('xm-select-hidn').css('display', 'none');
|
||||
|
||||
if (that.attr('fsw') != undefined) {
|
||||
if (that.is('i')) {
|
||||
var _ref12 = [$(e.target).parents('.layui-form-select').prev(), $(e.target).parent()],
|
||||
sel = _ref12[0],
|
||||
span = _ref12[1];
|
||||
|
||||
var val = {
|
||||
name: span.find('font').text(),
|
||||
val: span.attr('value')
|
||||
};
|
||||
var _options = commons.methods.getOptions(sel);
|
||||
_options = commons.data.confs.get(_options.name);
|
||||
_options.delete = true;
|
||||
commons.methods.getDiv(_options).find('dl dd[lay-value=\'' + val.val + '\']').click();
|
||||
setTimeout(function () {
|
||||
if (dl.hasClass('xm-select-show')) {
|
||||
div.addClass('layui-form-selected');
|
||||
dl.find('.xm-select-input-search').focus();
|
||||
} else {
|
||||
div.removeClass('layui-form-selected');
|
||||
}
|
||||
}, 3);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (block.find('select').attr('xm-select-radio') != undefined) {
|
||||
setTimeout(function () {
|
||||
if (dl.hasClass('xm-select-show')) {
|
||||
div.removeClass('layui-form-selected');
|
||||
dl.removeClass('xm-select-show').addClass('xm-select-hidn').css('display', 'none');
|
||||
} else {
|
||||
div.addClass('layui-form-selected');
|
||||
dl.removeClass('xm-select-hidn').addClass('xm-select-show').css('display', 'block');
|
||||
dl.find('.xm-select-input-search').focus();
|
||||
}
|
||||
}, 3);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dl.hasClass('xm-select-show')) {
|
||||
if (that.hasClass('xm-select') || that.hasClass('xm-select-empty')) {
|
||||
dl.removeClass('xm-select-show').addClass('xm-select-hidn').css('display', 'none');
|
||||
div.removeClass('layui-form-selected');
|
||||
} else {
|
||||
div.addClass('layui-form-selected');
|
||||
dl.find('.xm-select-input-search').focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
dl.removeClass('xm-select-hidn').addClass('xm-select-show').css('display', 'block');
|
||||
div.addClass('layui-form-selected');
|
||||
dl.find('.xm-select-input-search').focus();
|
||||
} else {
|
||||
$('.xm-select-parent > .layui-form-select > dl.xm-select-show').removeClass('xm-select-show').addClass('xm-select-hidn').css('display', 'none');
|
||||
}
|
||||
},
|
||||
listenClose: function listenClose() {
|
||||
$(document).on('click', function (e) {
|
||||
var that = $(e.target);
|
||||
var block = that.parents('.xm-select-parent');
|
||||
setTimeout(function () {
|
||||
commons.methods.listenCloseHandler(e, that, block);
|
||||
commons.methods.retop({ name: block.find('select[xm-select]').attr('xm-select') });
|
||||
$('.xm-select-parent input.xm-select-input-search').each(function (index, item) {
|
||||
var that = $(item);
|
||||
if (that.parents('dl').hasClass('xm-select-hidn')) {
|
||||
that.val('');
|
||||
that.parents('dl').find('.layui-hide').removeClass('layui-hide');
|
||||
that.parents('dl').find('p').remove();
|
||||
}
|
||||
});
|
||||
}, 10);
|
||||
});
|
||||
$(document).on({
|
||||
'input propertychange': function inputPropertychange(e) {
|
||||
var that = $(e.target);
|
||||
var dl = that.parents('dl');
|
||||
that.parents('dl').find('p').remove();
|
||||
dl.find('.layui-hide').removeClass('layui-hide');
|
||||
dl.find('dd:not(.layui-select-tips):not(:contains(\'' + that.val() + '\'))').addClass('layui-hide');
|
||||
if (!dl.find('dd:not(.layui-select-tips):not(.layui-hide)').length) {
|
||||
dl.append('<p class="xm-select-search-empty">\u65E0\u5339\u914D\u9879</p>');
|
||||
}
|
||||
dl.find('dt').each(function (index, item) {
|
||||
if (!$(item).nextUntil('dt', ':not(.layui-hide)').length) {
|
||||
$(item).addClass('layui-hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
}, '.xm-select-parent input.xm-select-input-search');
|
||||
},
|
||||
formRender: null,
|
||||
rewriteRender: function rewriteRender() {
|
||||
commons.methods.formRender = form.render;
|
||||
form.render = function (type, filter, repeat) {
|
||||
commons.methods.formRender(type, filter);
|
||||
if (filter) {
|
||||
var sel = $('[lay-filter=' + filter + ']').find('select[' + commons.data.name + ']');
|
||||
if (sel.length) {
|
||||
if (repeat) {
|
||||
commons.methods.init(commons.methods.getOptions(sel));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
commons.methods.autoInit(repeat);
|
||||
};
|
||||
},
|
||||
loadCss: function loadCss(skin, cs) {
|
||||
if (skin) {
|
||||
if (skin == 'default') {
|
||||
return;
|
||||
}
|
||||
var sn = void 0;
|
||||
if ($('style[skin]').length) {
|
||||
sn = $('style[skin]');
|
||||
} else {
|
||||
sn = $('<style ' + skin + ' type="text/css"></style>');
|
||||
}
|
||||
var df = styles.colors[skin] ? styles.colors[skin] : styles.colors.default;
|
||||
var colors = $.extend([], df, cs);
|
||||
sn.html(styles.cssColor(skin, colors[0], colors[1], colors[2], colors[3], colors[4], colors[5], colors[6]));
|
||||
sn.insertAfter($('head style:last'));
|
||||
} else {
|
||||
$('<style ' + commons.data.name + ' type="text/css">' + styles.css() + '</style>').insertBefore($('head *:first'));
|
||||
|
||||
var html = '';
|
||||
for (var name in styles.colors) {
|
||||
var _colors = styles.colors[name];
|
||||
if (_colors) {
|
||||
_colors = $.extend([], styles.colors.default, _colors);
|
||||
html += '<style ' + name + ' type="text/css">' + styles.cssColor(name, _colors[0], _colors[1], _colors[2], _colors[3], _colors[4], _colors[5], _colors[6]) + '</style>';
|
||||
}
|
||||
}
|
||||
if (html) {
|
||||
$(html).insertAfter($('head style[' + commons.data.name + ']'));
|
||||
}
|
||||
}
|
||||
},
|
||||
loadLastStyle: function loadLastStyle() {
|
||||
$('head').append('<style>' + styles.renderCss() + '</style>');
|
||||
},
|
||||
resize: function resize(selector, fun) {
|
||||
var id = commons.data.resize.get(selector);
|
||||
if (id != undefined) {
|
||||
clearInterval(id);
|
||||
}
|
||||
if (fun && fun instanceof Function) {
|
||||
var hisize = new Map();
|
||||
id = setInterval(function (e) {
|
||||
$(selector).each(function (index, item) {
|
||||
var thisize = [item.clientHeight, item.clientWidth];
|
||||
if (hisize.get(item)) {
|
||||
var his = hisize.get(item);
|
||||
if (his[0] != thisize[0] || his[1] != thisize[1]) {
|
||||
fun(item);
|
||||
}
|
||||
}
|
||||
hisize.set(item, thisize);
|
||||
});
|
||||
}, 250);
|
||||
commons.data.resize.set(selector, id);
|
||||
}
|
||||
},
|
||||
run: function run() {
|
||||
commons.methods.rewriteRender();
|
||||
commons.methods.listenClose();
|
||||
commons.methods.loadCss();
|
||||
commons.methods.loadLastStyle();
|
||||
commons.methods.autoInit();
|
||||
commons.methods.resize('.xm-select:not(.xm-select-height)', function (dom) {
|
||||
commons.methods.handlerDataTable(dom, dom.clientHeight + 'px');
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
styles = {
|
||||
colors: { //name, spanBgColor, spanColor, iBgColor, iColor, borderColor, tBgColor, tColor
|
||||
default: ['#F0F2F5', '#909399', '#C0C4CC', '#FFFFFF', '#F0F2F5', '#5FB878', '#FFFFFF'],
|
||||
primary: ['#009688', '#FFFFFF', '#009688', '#FFFFFF', '#009688', '#009688', '#FFFFFF'],
|
||||
normal: ['#1E9FFF', '#FFFFFF', '#1E9FFF', '#FFFFFF', '#1E9FFF', '#1E9FFF', '#FFFFFF'],
|
||||
warm: ['#FFB800', '#FFFFFF', '#FFB800', '#FFFFFF', '#FFB800', '#FFB800', '#FFFFFF'],
|
||||
danger: ['#FF5722', '#FFFFFF', '#FF5722', '#FFFFFF', '#FF5722', '#FF5722', '#FFFFFF']
|
||||
},
|
||||
css: function css() {
|
||||
return '\n\t\t\t\t\tselect[xm-select] + div .layui-select-title > input{display: none!important;}\n\t\t\t\t\tselect[xm-select] + div .layui-select-title > div.xm-select{\n\t\t\t\t\t\tline-height: normal;\n\t\t\t\t\t\theight: auto;\n\t\t\t\t\t\tpadding: 4px 10px;\n\t\t\t\t\t\toverflow: hidden;\n\t\t\t\t\t\tmin-height: 38px;\n\t\t\t\t\t\tleft: 0px;\n\t\t\t\t\t\tz-index: 99;\n\t\t\t\t\t\tposition: relative;\n\t\t\t\t\t\tbackground: none;\n\t\t\t\t\t\tpadding-right: 20px;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select] + div .layui-select-title > div.xm-select > span:not(.xm-select-empty){\n\t\t\t\t\t\tpadding: 2px 5px;\n\t\t\t\t\t\tbackground: #f0f2f5;\n\t\t\t\t\t\tborder-radius: 2px;\n\t\t\t\t\t\tcolor: #909399;\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t\tline-height: 18px;\n\t\t\t\t\t\theight: 18px;\n\t\t\t\t\t\tmargin: 2px 5px 2px 0px;\n\t\t\t\t\t\tfloat: left;\n\t\t\t\t\t\tcursor: initial;\n\t\t\t\t\t\tuser-select: none;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select] + div .layui-select-title > div.xm-select > span:not(.xm-select-empty) i{\n\t\t\t\t\t\tbackground-color: #c0c4cc;\n\t\t\t\t\t\tcolor: #ffffff;\n\t\t\t\t\t\tmargin-left: 8px;\n\t\t\t\t\t\tborder-radius: 20px;\n\t\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\t\tcursor: initial;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select] + div .layui-select-title > div.xm-select > span:not(.xm-select-empty) i:hover{\n\t\t\t\t\t\tbackground-color: #909399;\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select] + div .layui-select-title > div.xm-select > span.xm-select-empty{\n\t\t\t\t\t\tdisplay: inline-block;\n\t\t\t\t\t\theight: 28px;\n\t\t\t\t\t\tline-height: 28px;\n\t\t\t\t\t\tcolor: #999999\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select] + div dl dd.xm-select-dd-search{\n\t\t\t\t\t\tbackground-color: #FFFFFF;\n\t\t\t\t\t\tpadding-bottom: 5px;\n\t\t\t\t\t\tmargin-right: 30px;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select] + div dl dd.xm-select-dd-search > input{\n\t\t\t\t\t\tcursor: text;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select] + div dl dd.xm-select-dd-search > span{\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t right: 8px;\n\t\t\t\t\t top: 5px;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select] + div dl dd.xm-select-dd-search > span > i{\n\t\t\t\t\t\tfont-size: 22px;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select] + div dl dd.xm-select-dd-search > span > i:hover{\n\t\t\t\t\t\tcolor: red;\n\t\t\t\t\t\topacity:.8;\n\t\t\t\t\t\tfilter:alpha(opacity=80);\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select] + div dl p.xm-select-search-empty{\n\t\t\t\t\t\tmargin: 10px 0;\n\t\t\t\t\t text-align: center;\n\t\t\t\t\t color: #999;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select][xm-select-type=\'1\'] + div dl dd.xm-select-this,select[xm-select]:not([xm-select-type]) + div dl dd.xm-select-this{\n\t\t\t\t\t\tbackground-color: #5FB878;\n\t\t\t\t\t\tcolor: #FFF;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select][xm-select-type=\'1\'] + div dl dd.layui-disabled,select[xm-select]:not([xm-select-type]) + div dl dd.layui-disabled{\n\t\t\t\t\t\tbackground-color: #FFFFFF;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select][xm-select-type=\'2\'] + div dl dd.layui-disabled i{\n\t\t\t\t\t\tborder-color: #C2C2C2;\n\t\t\t\t\t\tcolor: #FFFFFF;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select][xm-select-type=\'2\'] + div dl dd.xm-select-this.layui-disabled i{\n\t\t\t\t\t\tbackground-color: #D2D2D2;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select][xm-select-type=\'2\'] + div dl dd.xm-select-this:not(.layui-disabled) i{\n\t\t\t\t\t\tbackground-color: #5FB878;\n\t\t\t\t\t\tborder-color: #5FB878;\n\t\t\t\t\t\tcolor: #FFF;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select][xm-select-type=\'3\'] + div dl dd.xm-select-this:not(.layui-disabled){\n\t\t\t\t\t\tcolor: #5FB878;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select][xm-select-type=\'3\'] + div dl dd.xm-select-this i{\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\tright: 10px;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t';
|
||||
},
|
||||
cssColor: function cssColor(name, spanBgColor, spanColor, iBgColor, iColor, borderColor, tBgColor, tColor) {
|
||||
return '\n\t\t\t\t\tselect[xm-select][xm-select-skin=\'' + name + '\'] + div .layui-select-title > div.xm-select > span:not(.xm-select-empty){\n\t\t\t\t\t\tbackground: ' + spanBgColor + ';\n\t\t\t\t\t\tcolor: ' + spanColor + ';\n\t\t\t\t\t\tborder: 1px solid ' + borderColor + '\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select][xm-select-skin=\'' + name + '\'] + div .layui-select-title > div.xm-select > span:not(.xm-select-empty) i{\n\t\t\t\t\t\tbackground: ' + iBgColor + ';\n\t\t\t\t\t\tcolor: ' + iColor + ';\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select][xm-select-skin=\'' + name + '\'] + div .layui-select-title > div.xm-select > span:not(.xm-select-empty) i:hover{\n\t\t\t\t\t\topacity:.8;\n\t\t\t\t\t\tfilter:alpha(opacity=80);\n\t\t\t\t\t\tcursor: pointer;\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select][xm-select-skin=\'' + name + '\'][xm-select-type=\'1\'] + div dl dd.xm-select-this:not(.layui-disabled),select[xm-select][xm-select-skin=\'' + name + '\']:not([xm-select-type]) + div dl dd.xm-select-this:not(.layui-disabled){\n\t\t\t\t\t\tbackground-color: ' + tBgColor + ';\n\t\t\t\t\t\tcolor: ' + tColor + ';\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select][xm-select-skin=\'' + name + '\'][xm-select-type=\'2\'] + div dl dd.xm-select-this:not(.layui-disabled) i{\n\t\t\t\t\t\tbackground-color: ' + tBgColor + ';\n\t\t\t\t\t\tborder-color: ' + tBgColor + ';\n\t\t\t\t\t\tcolor: ' + tColor + ';\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select][xm-select-skin=\'' + name + '\'][xm-select-type=\'3\'] + div dl dd.xm-select-this:not(.layui-disabled){\n\t\t\t\t\t\tcolor: ' + tBgColor + ';\n\t\t\t\t\t}\n\t\t\t\t\tselect[xm-select][xm-select-type=\'3\'] + div dl dd.xm-select-this i{\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\tright: 10px;\n\t\t\t\t\t}\n\t\t\t\t';
|
||||
},
|
||||
renderCss: function renderCss() {
|
||||
return '\n\t\t\t\t\tselect[xm-select] + div .layui-select-title > div.xm-select > span:not(.xm-select-empty){\n\t\t\t\t\t\tbox-sizing: content-box;\n\t\t\t\t\t}\n\t\t\t\t\t.xm-select-parent .layui-form-select dl dd.layui-this{\n\t\t\t\t\t background-color: inherit;\n\t\t\t\t\t color: inherit;\n\t\t\t\t\t}\n\t\t\t\t\t.xm-select-parent .layui-form-select dl dd.layui-this.layui-select-tips {\n\t\t\t\t\t\tbackground-color: #FFFFFF;\n\t\t\t\t\t color: #999999;\n\t\t\t\t\t}\n\t\t\t\t\t.xm-select-parent .layui-form-selected dl {\n\t\t\t\t\t display: none;\n\t\t\t\t\t}\n\t\t\t\t\t.xm-select-show:{\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t}\n\t\t\t\t\t.xm-select-hidn:{\n\t\t\t\t\t\tdisplay: none;\n\t\t\t\t\t}\n\t\t\t\t';
|
||||
}
|
||||
};
|
||||
commons.methods.run();
|
||||
return select3;
|
||||
});
|
||||
@@ -74,7 +74,7 @@
|
||||
title:"新增禁用命令",
|
||||
content: '/ban/add',
|
||||
area:['500px','250px'],
|
||||
cancel:function(){
|
||||
cancel:function(index){
|
||||
window.location.reload();
|
||||
layer.close(index);
|
||||
}
|
||||
@@ -92,7 +92,7 @@
|
||||
title:"编辑禁用命令",
|
||||
content: '/ban/edit?id='+data.id,
|
||||
area:['500px','250px'],
|
||||
cancel:function(){
|
||||
cancel:function(index){
|
||||
window.location.reload();
|
||||
layer.close(index);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
|
||||
<div class="layui-footer" style="border-top: 2px solid #e4e4e4">
|
||||
<!-- 底部固定区域 -->
|
||||
© Power by <a href="http://www.haodaquan.com/" target="_blank">PPGo_Job V2.6 </a>
|
||||
© Power by <a href="http://www.haodaquan.com/" target="_blank">PPGo_Job {{.version}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/layui/layui.js"></script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="layui-layout layui-layout-admin" style="padding-left: 40px;margin-top: 20px;">
|
||||
<div style="margin: 10px 0px">
|
||||
<blockquote class="layui-elem-quote">
|
||||
说明:提交之前请先测试服务器资源是否可以连通
|
||||
说明:提交之前请先测试执行器资源是否可以连通
|
||||
</blockquote>
|
||||
</div>
|
||||
<form class="layui-form" action="" method="post" >
|
||||
|
||||
@@ -23,12 +23,13 @@
|
||||
<div class="layui-input-inline mw400">
|
||||
<input type="radio" name="connection_type" lay-verify="type" value="0" title="SSH" {{if eq .server.connection_type 0}}checked{{end}}>
|
||||
<input type="radio" name="connection_type" lay-verify="type" value="1" title="Telnet" {{if eq .server.connection_type 1}}checked{{end}}>
|
||||
<input type="radio" name="connection_type" lay-verify="type" value="2" title="Agent" {{if eq .server.connection_type 2}}checked{{end}}>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200">服务器名称</label>
|
||||
<label class="layui-form-label mw200">器名称</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
<input type="text" name="server_name" id="server_name" lay-verify="required" autocomplete="off" placeholder="服务器名称" class="layui-input" value="{{.server.server_name}}">
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<div class="layui-input-inline mw400">
|
||||
<input type="radio" name="connection_type" lay-verify="type" value="0" title="SSH" {{if eq .server.connection_type 0}}checked{{end}}>
|
||||
<input type="radio" name="connection_type" lay-verify="type" value="1" title="Telnet" {{if eq .server.connection_type 1}}checked{{end}}>
|
||||
<input type="radio" name="connection_type" lay-verify="type" value="2" title="Agent" {{if eq .server.connection_type 2}}checked{{end}}>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<form class="layui-form" action="" onsubmit="javascript:return false;">
|
||||
<div class="demoTable">
|
||||
<div class="layui-inline" style="width: 40%">
|
||||
<input class="layui-input" name="serverName" id="serverName" autocomplete="off" placeholder="资源名称" >
|
||||
<input class="layui-input" name="serverName" id="serverName" autocomplete="off" placeholder="执行资源名称" >
|
||||
</div>
|
||||
<div class="layui-inline" style="width: 20%;text-align: left;">
|
||||
<select name="serverGroupId" id="serverGroupId">
|
||||
@@ -53,13 +53,12 @@
|
||||
,url: '/server/table'
|
||||
,cols: [[
|
||||
// {checkbox: true, fixed: true},
|
||||
{field:'id', title: 'ID', align:'center',sort: true, width:150}
|
||||
,{field:'server_name',title: '资源名称'}
|
||||
,{field:'group_name',title: '分组名称'}
|
||||
,{field:'connection_type', title: '连接类型'}
|
||||
,{field:'type', title: '登录类型'}
|
||||
{field:'id', title: 'ID', align:'center',sort: true, width:80}
|
||||
,{field:'server_name',title: '执行资源名称',width:350}
|
||||
,{field:'connection_type', title: '类型',width:100}
|
||||
,{field:'ip_port', title: 'IP端口'}
|
||||
,{field:'group_name',title: '分组名称',width:200}
|
||||
,{field:'detail', title: '备注'}
|
||||
// ,{field:'status_text', title: '状态'}
|
||||
,{fixed: 'right', width:160, align:'center', toolbar: '#bar'}
|
||||
]]
|
||||
,id: 'listReload'
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="layui-layout layui-layout-admin" style="padding-left: 40px;margin-top: 20px;">
|
||||
<form class="layui-form" action="" method="post">
|
||||
<div class="layui-form-item">
|
||||
@@ -14,7 +15,7 @@
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200">所属分组</label>
|
||||
<label class="layui-form-label mw200">任务分组</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name="group_id" lay-verify="required">
|
||||
{{range $k, $v := .taskGroup}}
|
||||
@@ -25,28 +26,35 @@
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200">服务器资源</label>
|
||||
<div class="layui-block">
|
||||
<div class="layui-col-md3"></div>
|
||||
<div class="layui-col-md9">
|
||||
<div style="margin-bottom: 20px;">
|
||||
<input type="checkbox" name="server_id" lay-skin="primary" title="本地服务器" value="0">
|
||||
</div>
|
||||
<div class="layui-row layui-col-space10">
|
||||
<label class="layui-form-label mw200">执行资源</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
<select name="server_id" lay-verify="required" lay-search xm-select="select_server" >
|
||||
<option value="">请选择</option>
|
||||
<option value="0">本机执行</option>
|
||||
{{range $k, $v := .serverGroup}}
|
||||
<div class="layui-col-md12" ><i class="layui-icon"></i> {{$v.GroupName}}</div>
|
||||
<optgroup label="{{$v.GroupName}}">
|
||||
{{range $kk, $vv := $v.Servers}}
|
||||
<div class="layui-col-md4">
|
||||
<input type="checkbox" name="server_id" lay-skin="primary" title="{{$vv}}" value="{{$kk}}">
|
||||
</div>
|
||||
<option value="{{$kk}}">{{$vv}}</option>
|
||||
{{end}}
|
||||
</optgroup>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200">执行策略</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
<input type="radio" name="server_type" value="0" lay-filter="server_type" title="同时执行" checked>
|
||||
<input type="radio" name="server_type" value="1" lay-filter="server_type" title="轮询执行">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200">任务说明</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
@@ -71,7 +79,7 @@
|
||||
<label class="layui-form-label mw200">时间表达式</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
<input type="text" name="cron_spec" id="cron_spec" lay-verify="required" autocomplete="off"
|
||||
placeholder="时间表达式" class="layui-input" value="">
|
||||
placeholder="*/40 * * * * " class="layui-input" value="">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux"><a href="/help" target="_blank"><i class="fa fa-question-circle"
|
||||
aria-hidden="true"></i></a>
|
||||
@@ -81,7 +89,7 @@
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200">命令脚本</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
<textarea name="command" id="command" rows="5" placeholder="请输入命令内容" class="layui-textarea"></textarea>
|
||||
<textarea name="command" id="command" rows="5" placeholder="echo hello world && ls -a" class="layui-textarea"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -125,14 +133,16 @@
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="layui-form-item notify">
|
||||
<label class="layui-form-label mw200">通知用户</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
<select name="notify_user" lay-search xm-select="select_notify" >
|
||||
<option value="">请选择</option>
|
||||
{{range $k, $v := .adminInfo}}
|
||||
<input type="checkbox" name="notify_user" lay-filter="notify_user" title="{{$v.RealName}}"
|
||||
value="{{$v.Id}}" lay-skin="primary">
|
||||
<option value="{{$v.Id}}">{{$v.RealName}}</option>
|
||||
{{end}}
|
||||
|
||||
</select>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
@@ -148,9 +158,25 @@
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
layui.use(['form', 'element', 'layer', 'jquery'], function () {
|
||||
|
||||
layui.config({
|
||||
base:'/static/admin/js/'
|
||||
}).extend({
|
||||
formSelects: 'formSelects-v3'
|
||||
});
|
||||
layui.use(['form', 'element', 'layer', 'jquery','formSelects'], function () {
|
||||
var form = layui.form; //只有执行了这一步,部分表单元素才会自动修饰成功
|
||||
var $ = layui.jquery;
|
||||
var formSelects = layui.formSelects;
|
||||
|
||||
formSelects.render({
|
||||
name : 'select_server',
|
||||
});
|
||||
|
||||
formSelects.render({
|
||||
name : 'select_notify',
|
||||
});
|
||||
|
||||
var error_info = "{{.flash.error}}";
|
||||
if (error_info) {
|
||||
layer.msg(error_info, {icon: 2, shade: 0.3}, function () {
|
||||
@@ -165,20 +191,6 @@
|
||||
});
|
||||
|
||||
|
||||
var notify_user_ids = [];
|
||||
form.on('checkbox(notify_user)', function (data) {
|
||||
if (data.elem.checked == true) {
|
||||
notify_user_ids.push(data.value)
|
||||
} else {
|
||||
$.each(notify_user_ids, function (index, item) {
|
||||
// index是索引值(即下标) item是每次遍历得到的值;
|
||||
if (item == data.value) {
|
||||
notify_user_ids.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
$("#notify_user_ids").val(notify_user_ids.join(","));
|
||||
});
|
||||
|
||||
form.on('radio(is_notify)', function (data) {
|
||||
if (data.value == 1) {
|
||||
@@ -191,10 +203,18 @@
|
||||
form.on('submit(sub)', function (data) {
|
||||
|
||||
var form_data = data.field;
|
||||
|
||||
//选择执行器
|
||||
var ids = formSelects.value('select_server'); //获取选中的
|
||||
if (ids.length<1){
|
||||
layer.msg("请选择执行资源");
|
||||
return false;
|
||||
}
|
||||
|
||||
var server_arr = new Array();
|
||||
$("input:checkbox[name=server_id]:checked").each(function(){
|
||||
server_arr.push($(this).val());
|
||||
});
|
||||
$.each(ids,function (k,v) {
|
||||
server_arr.push(v.val);
|
||||
})
|
||||
|
||||
form_data.server_ids = server_arr.join(",");
|
||||
|
||||
@@ -203,17 +223,28 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
var notify_user_arr = new Array();
|
||||
$("input:checkbox[name=notify_user]:checked").each(function(){
|
||||
notify_user_arr.push($(this).val());
|
||||
});
|
||||
form_data.notify_user_ids = notify_user_arr.join(",");
|
||||
|
||||
if (form_data.is_notify==1 && form_data.notify_user_ids==="") {
|
||||
layer.msg("请选择通知用户");
|
||||
//选择通知人
|
||||
if (form_data.is_notify==1 ) {
|
||||
var ids = formSelects.value('select_notify'); //获取选中的
|
||||
if (ids.length<1){
|
||||
layer.msg("请选择通知人");
|
||||
return false;
|
||||
}
|
||||
|
||||
var notify_arr = new Array();
|
||||
$.each(ids,function (k,v) {
|
||||
notify_arr.push(v.val);
|
||||
})
|
||||
|
||||
form_data.notify_user_ids = notify_arr.join(",");
|
||||
|
||||
if (form_data.notify_user_ids==="" || form_data.notify_user_ids===null){
|
||||
layer.msg("请选择通知人");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$.post('{{urlfor "TaskController.AjaxSave"}}', form_data, function (out) {
|
||||
if (out.status == 0) {
|
||||
layer.msg("操作成功", {icon: 1, shade: 0.3, time: 1000}, function () {
|
||||
|
||||
@@ -53,11 +53,11 @@
|
||||
,url: '/task/table?status=2'
|
||||
,cols: [[
|
||||
{checkbox: true, fixed: true},
|
||||
{field:'id', title: 'ID', align:'center',sort: true, width:50}
|
||||
{field:'id', title: 'ID', align:'center',sort: true, width:100}
|
||||
,{field:'task_name',title: '任务名称'}
|
||||
,{field:'description',title: '任务说明'}
|
||||
,{field:'cron_spec',title: '执行时间', width:150}
|
||||
,{field:'pre_time', width:170,title: '上次执行时间'}
|
||||
,{field:'execute_times', width:70,title: '次数'}
|
||||
,{field:'execute_times', width:100,title: '次数'}
|
||||
,{fixed:'right', width:150, align:'center', title:'操作', toolbar: '#bar'}
|
||||
]]
|
||||
,id: 'listReload'
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
}
|
||||
</style>
|
||||
<div class="layui-layout layui-layout-admin" style="padding-left: 40px;margin-top: 20px;">
|
||||
<form class="layui-form" action="" method="post" >
|
||||
<form class="layui-form" action="javascript:void(0)" method="post" >
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200">任务名称</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
@@ -24,23 +24,32 @@
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200">服务器资源</label>
|
||||
<div class="layui-inline">
|
||||
|
||||
<div class="layui-input-inline">
|
||||
<select name="server_id">
|
||||
<option value="0">本地服务器</option>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200">执行资源</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
<select name="server_id" xm-select="select_server" >
|
||||
<option value="">请选择</option>
|
||||
<option value="0">本机执行</option>
|
||||
{{range $k, $v := .serverGroup}}
|
||||
<optgroup label="{{$v.GroupName}}">
|
||||
{{range $kk, $vv := $v.Servers}}
|
||||
<option value="{{$kk}}" {{if eq $kk $.task.ServerId}}selected{{end}} >{{$vv}}</option>
|
||||
<option value="{{$kk}}">{{$vv}}</option>
|
||||
{{end}}
|
||||
</optgroup>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200">执行策略</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
<input type="radio" name="server_type" value="0" lay-filter="server_type" title="同时执行" {{if eq .task.ServerType 0}}checked{{end}}>
|
||||
<input type="radio" name="server_type" value="1" lay-filter="server_type" title="轮询执行" {{if eq .task.ServerType 1}}checked{{end}}>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -95,13 +104,25 @@
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item notify">
|
||||
<hr>
|
||||
<label class="layui-form-label mw200">通知类型</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
<input type="radio" name="notify_type" lay-verify="required" value="0" title="邮件" {{if eq .task.NotifyType 0}}checked{{end}}>
|
||||
<input type="radio" name="notify_type" lay-verify="required" value="1" title="短信" {{if eq .task.NotifyType 1}}checked{{end}}>
|
||||
<input type="radio" name="notify_type" lay-verify="required" value="2" title="钉钉" {{if eq .task.NotifyType 2}}checked{{end}}>
|
||||
<input type="radio" name="notify_type" lay-verify="required" value="3" title="微信" {{if eq .task.NotifyType 3}}checked{{end}}>
|
||||
<input type="radio" name="notify_type" value="0" lay-filter="notify_type" title="邮件" {{if eq .task.NotifyType 0}}checked{{end}}>
|
||||
<input type="radio" name="notify_type" value="1" lay-filter="notify_type" title="短信" {{if eq .task.NotifyType 1}}checked{{end}}>
|
||||
<input type="radio" name="notify_type" value="2" lay-filter="notify_type" title="钉钉" {{if eq .task.NotifyType 2}}checked{{end}}>
|
||||
<input type="radio" name="notify_type" value="3" lay-filter="notify_type" title="微信" {{if eq .task.NotifyType 3}}checked{{end}}>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item notify">
|
||||
<label class="layui-form-label mw200">通知模板</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name="notify_tpl_id" lay-filter="notify_tpl_id">
|
||||
<option value="0">请选择</option>
|
||||
{{range $k, $v := .notifyTpl}}
|
||||
<option value="{{$v.id}}" {{if eq $.task.NotifyTplId $v.id}} selected {{end}}>{{$v.tpl_name}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
@@ -109,14 +130,16 @@
|
||||
<div class="layui-form-item notify">
|
||||
<label class="layui-form-label mw200">通知用户</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
<select name="notify_user" lay-search xm-select="select_notify" >
|
||||
<option value="">请选择</option>
|
||||
{{range $k, $v := .adminInfo}}
|
||||
<input type="checkbox" name="notify_user" lay-filter="notify_user" title="{{$v.RealName}}" value="{{$v.Id}}" lay-skin="primary" {{range $ks,$vs:=$.notify_user_ids}} {{if eq $v.Id $vs}}checked{{end}}{{end}}>
|
||||
<option value="{{$v.Id}}">{{$v.RealName}}</option>
|
||||
{{end}}
|
||||
<input type="hidden" name="notify_user_ids" id="notify_user_ids" value="{{.task.NotifyUserIds}}">
|
||||
</select>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
<input type="hidden" name="id" id="id" value="0">
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200"></label>
|
||||
<div class="layui-input-inline mw400">
|
||||
@@ -127,16 +150,38 @@
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
layui.use(['form','element','layer','jquery'],function(){
|
||||
layui.config({
|
||||
base:'/static/admin/js/'
|
||||
}).extend({
|
||||
formSelects: 'formSelects-v3'
|
||||
}).use(['form','element','layer','jquery','formSelects'],function(){
|
||||
var form = layui.form; //只有执行了这一步,部分表单元素才会自动修饰成功
|
||||
var $ = layui.jquery;
|
||||
|
||||
var formSelects = layui.formSelects;
|
||||
|
||||
var server_id = {{$.service_ids}};
|
||||
formSelects.render({
|
||||
init : server_id,
|
||||
name : 'select_server',
|
||||
});
|
||||
|
||||
var select_notify = {{$.notify_user_ids}};
|
||||
|
||||
console.log(select_notify);
|
||||
formSelects.render({
|
||||
init : select_notify,
|
||||
name : 'select_notify',
|
||||
});
|
||||
|
||||
var error_info = "{{.flash.error}}";
|
||||
if(error_info){
|
||||
layer.msg(error_info,{icon: 2,shade:0.3},function () {
|
||||
window.history.go(-1)
|
||||
self.location=document.referrer;
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
$("#des").on('click',function () {
|
||||
layer.tips('设为“是”的话,如果该任务在上一个时间点还没执行完,则略过不执行', '#des', {
|
||||
tips: [1, '#0FA6D8'] //还可配置颜色
|
||||
@@ -154,34 +199,91 @@
|
||||
}
|
||||
});
|
||||
|
||||
var notify_user_ids = [];
|
||||
form.on('checkbox(notify_user)', function(data){
|
||||
if(data.elem.checked==true){
|
||||
notify_user_ids.push(data.value)
|
||||
}else{
|
||||
$.each(notify_user_ids,function(index,item){
|
||||
// index是索引值(即下标) item是每次遍历得到的值;
|
||||
if(item==data.value){
|
||||
notify_user_ids.splice(index,1);
|
||||
form.on('submit(sub)', function(data){
|
||||
var isAdmin = "{{.isAdmin}}";
|
||||
var msg = "编辑任务需要重新审核,是否确认需要编辑?";
|
||||
var okmsg = "操作成功,可去【任务审核】中查看该任务";
|
||||
|
||||
if (isAdmin==1){
|
||||
msg = "是否确认需要编辑?";
|
||||
okmsg = "操作成功";
|
||||
}
|
||||
});
|
||||
|
||||
layer.confirm(msg, {icon: 3, title:'提示'}, function(index){
|
||||
var form_data = data.field;
|
||||
|
||||
//选择执行器
|
||||
var ids = formSelects.value('select_server'); //获取选中的
|
||||
if (ids.length<1){
|
||||
layer.msg("请选择执行资源");
|
||||
return false;
|
||||
}
|
||||
$("#notify_user_ids").val(notify_user_ids.join(","));
|
||||
|
||||
var server_arr = new Array();
|
||||
$.each(ids,function (k,v) {
|
||||
server_arr.push(v.val);
|
||||
})
|
||||
|
||||
form_data.server_ids = server_arr.join(",");
|
||||
|
||||
if (form_data.server_ids==="" || form_data.server_ids===null){
|
||||
layer.msg("请选择服务器资源");
|
||||
return false;
|
||||
}
|
||||
|
||||
//选择通知人
|
||||
if (form_data.is_notify==1 ) {
|
||||
var ids = formSelects.value('select_notify'); //获取选中的
|
||||
if (ids.length<1){
|
||||
layer.msg("请选择通知人");
|
||||
return false;
|
||||
}
|
||||
|
||||
var notify_arr = new Array();
|
||||
$.each(ids,function (k,v) {
|
||||
notify_arr.push(v.val);
|
||||
});
|
||||
|
||||
form.on('submit(sub)', function(data){
|
||||
var form_data = data.field;
|
||||
form_data.notify_user_ids = notify_arr.join(",");
|
||||
if (form_data.notify_user_ids==="" || form_data.notify_user_ids===null){
|
||||
layer.msg("请选择通知人");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$.post('{{urlfor "TaskController.AjaxSave"}}', form_data, function (out) {
|
||||
if (out.status == 0) {
|
||||
layer.msg("复制成功!,请到列表中查看",{icon: 1,shade:0.3,time:1000},function () {
|
||||
layer.msg(okmsg,{icon: 1,shade:0.3,time:1000},function () {
|
||||
// self.location=document.referrer;
|
||||
window.parent.deleteCurrentTab();
|
||||
})
|
||||
} else {
|
||||
layer.msg(out.message);
|
||||
return false;
|
||||
}
|
||||
}, "json");
|
||||
|
||||
setTimeout(function(){
|
||||
layer.closeAll('loading');
|
||||
}, 2000);
|
||||
layer.close(index);
|
||||
});
|
||||
});
|
||||
|
||||
form.on('radio(notify_type)', function (data) {
|
||||
$.post('{{urlfor "TaskController.AjaxNotifyType"}}', {notify_type: data.value}, function (out) {
|
||||
if (out.code == 0) {
|
||||
$('select[name="notify_tpl_id"]').empty();
|
||||
$('<option value="0" selected>请选择</option>').appendTo('select[name="notify_tpl_id"]');
|
||||
for (var i = 0; i < out.data.length; i++) {
|
||||
$('<option value="' + out.data[i].id + '">' + out.data[i].tpl_name + '</option>').appendTo('select[name="notify_tpl_id"]');
|
||||
}
|
||||
|
||||
form.render();
|
||||
} else {
|
||||
layer.msg(out.message)
|
||||
}
|
||||
}, "json");
|
||||
return false;
|
||||
});
|
||||
|
||||
//但是,如果你的HTML是动态生成的,自动渲染就会失效
|
||||
|
||||
@@ -64,8 +64,14 @@
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>服务资源</td>
|
||||
<td> {{.serverName}}</td>
|
||||
<td>执行资源</td>
|
||||
<td> {{str2html .serverName}}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>执行策略</td>
|
||||
<td> {{.ServerType}}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -114,7 +120,7 @@
|
||||
<td>通知用户</td>
|
||||
<td>
|
||||
{{range $k, $v := .adminInfo}}
|
||||
{{$v.RealName}}
|
||||
{{$v.RealName}} <br>
|
||||
{{end}}
|
||||
</td>
|
||||
<td></td>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
}
|
||||
</style>
|
||||
<div class="layui-layout layui-layout-admin" style="padding-left: 40px;margin-top: 20px;">
|
||||
<form class="layui-form" action="javascript:return false;" method="post" >
|
||||
<form class="layui-form" action="javascript:void(0)" method="post" >
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200">任务名称</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
@@ -24,29 +24,35 @@
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200">服务器资源</label>
|
||||
<div class="layui-block">
|
||||
<div class="layui-col-md3"></div>
|
||||
<div class="layui-col-md9">
|
||||
<div style="margin-bottom: 20px;">
|
||||
<input type="checkbox" name="server_id" lay-skin="primary" title="本地服务器" value="0" {{range $ks,$vs:=$.service_ids}} {{if eq 0 $vs}}checked{{end}}{{end}}>
|
||||
</div>
|
||||
<div class="layui-row layui-col-space10">
|
||||
<label class="layui-form-label mw200">执行资源</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
<select name="server_id" xm-select="select_server" >
|
||||
<option value="">请选择</option>
|
||||
<option value="0">本机执行</option>
|
||||
{{range $k, $v := .serverGroup}}
|
||||
<div class="layui-col-md12" ><i class="layui-icon"></i> {{$v.GroupName}}</div>
|
||||
<optgroup label="{{$v.GroupName}}">
|
||||
{{range $kk, $vv := $v.Servers}}
|
||||
<div class="layui-col-md4">
|
||||
<input type="checkbox" name="server_id" lay-skin="primary" title="{{$vv}}" value="{{$kk}}" {{range $ks,$vs:=$.service_ids}} {{if eq $kk $vs}}checked{{end}}{{end}}>
|
||||
</div>
|
||||
<option value="{{$kk}}">{{$vv}}</option>
|
||||
{{end}}
|
||||
</optgroup>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200">执行策略</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
<input type="radio" name="server_type" value="0" lay-filter="server_type" title="同时执行" {{if eq .task.ServerType 0}}checked{{end}}>
|
||||
<input type="radio" name="server_type" value="1" lay-filter="server_type" title="轮询执行" {{if eq .task.ServerType 1}}checked{{end}}>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200">任务说明</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
@@ -124,13 +130,17 @@
|
||||
<div class="layui-form-item notify">
|
||||
<label class="layui-form-label mw200">通知用户</label>
|
||||
<div class="layui-input-inline mw400">
|
||||
<select name="notify_user" lay-search xm-select="select_notify" >
|
||||
<option value="">请选择</option>
|
||||
{{range $k, $v := .adminInfo}}
|
||||
<input type="checkbox" name="notify_user" lay-filter="notify_user" title="{{$v.RealName}}" value="{{$v.Id}}" lay-skin="primary" {{range $ks,$vs:=$.notify_user_ids}} {{if eq $v.Id $vs}}checked{{end}}{{end}}>
|
||||
<option value="{{$v.Id}}">{{$v.RealName}}</option>
|
||||
{{end}}
|
||||
<input type="hidden" name="notify_user_ids" id="notify_user_ids" value="{{.task.NotifyUserIds}}">
|
||||
</select>
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<input type="hidden" name="id" id="id" value="{{.task.Id}}">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label mw200"></label>
|
||||
@@ -142,10 +152,30 @@
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
layui.use(['form','element','layer','jquery'],function(){
|
||||
layui.config({
|
||||
base:'/static/admin/js/'
|
||||
}).extend({
|
||||
formSelects: 'formSelects-v3'
|
||||
}).use(['form','element','layer','jquery','formSelects'],function(){
|
||||
var form = layui.form; //只有执行了这一步,部分表单元素才会自动修饰成功
|
||||
var $ = layui.jquery;
|
||||
|
||||
var formSelects = layui.formSelects;
|
||||
|
||||
var server_id = {{$.service_ids}};
|
||||
formSelects.render({
|
||||
init : server_id,
|
||||
name : 'select_server',
|
||||
});
|
||||
|
||||
var select_notify = {{$.notify_user_ids}};
|
||||
|
||||
console.log(select_notify);
|
||||
formSelects.render({
|
||||
init : select_notify,
|
||||
name : 'select_notify',
|
||||
});
|
||||
|
||||
var error_info = "{{.flash.error}}";
|
||||
if(error_info){
|
||||
layer.msg(error_info,{icon: 2,shade:0.3},function () {
|
||||
@@ -171,22 +201,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
var notify_user_ids = [];
|
||||
form.on('checkbox(notify_user)', function(data){
|
||||
if(data.elem.checked==true){
|
||||
notify_user_ids.push(data.value)
|
||||
}else{
|
||||
$.each(notify_user_ids,function(index,item){
|
||||
// index是索引值(即下标) item是每次遍历得到的值;
|
||||
if(item==data.value){
|
||||
notify_user_ids.splice(index,1);
|
||||
}
|
||||
});
|
||||
}
|
||||
$("#notify_user_ids").val(notify_user_ids.join(","));
|
||||
});
|
||||
|
||||
|
||||
form.on('submit(sub)', function(data){
|
||||
var isAdmin = "{{.isAdmin}}";
|
||||
var msg = "编辑任务需要重新审核,是否确认需要编辑?";
|
||||
@@ -198,19 +212,46 @@
|
||||
}
|
||||
|
||||
layer.confirm(msg, {icon: 3, title:'提示'}, function(index){
|
||||
|
||||
var form_data = data.field;
|
||||
var ids = [];
|
||||
$("input[name=server_id][type=checkbox]").each(function() {
|
||||
if ($(this).prop("checked")) {
|
||||
ids.push($(this).val());
|
||||
}
|
||||
});
|
||||
|
||||
//选择执行器
|
||||
var ids = formSelects.value('select_server'); //获取选中的
|
||||
if (ids.length<1){
|
||||
layer.msg("请选择服务资源");
|
||||
layer.msg("请选择执行资源");
|
||||
return false;
|
||||
}
|
||||
form_data.server_ids = ids.join(",");
|
||||
|
||||
var server_arr = new Array();
|
||||
$.each(ids,function (k,v) {
|
||||
server_arr.push(v.val);
|
||||
})
|
||||
|
||||
form_data.server_ids = server_arr.join(",");
|
||||
|
||||
if (form_data.server_ids==="" || form_data.server_ids===null){
|
||||
layer.msg("请选择服务器资源");
|
||||
return false;
|
||||
}
|
||||
|
||||
//选择通知人
|
||||
if (form_data.is_notify==1 ) {
|
||||
var ids = formSelects.value('select_notify'); //获取选中的
|
||||
if (ids.length<1){
|
||||
layer.msg("请选择通知人");
|
||||
return false;
|
||||
}
|
||||
|
||||
var notify_arr = new Array();
|
||||
$.each(ids,function (k,v) {
|
||||
notify_arr.push(v.val);
|
||||
});
|
||||
|
||||
form_data.notify_user_ids = notify_arr.join(",");
|
||||
if (form_data.notify_user_ids==="" || form_data.notify_user_ids===null){
|
||||
layer.msg("请选择通知人");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$.post('{{urlfor "TaskController.AjaxSave"}}', form_data, function (out) {
|
||||
if (out.status == 0) {
|
||||
@@ -219,8 +260,8 @@
|
||||
window.parent.deleteCurrentTab();
|
||||
})
|
||||
} else {
|
||||
layer.msg(out.message)
|
||||
return
|
||||
layer.msg(out.message);
|
||||
return false;
|
||||
}
|
||||
}, "json");
|
||||
|
||||
|
||||
@@ -58,12 +58,12 @@
|
||||
,url: '/task/table'
|
||||
,cols: [[
|
||||
{checkbox: true, fixed: true},
|
||||
{field:'id', title: 'ID', align:'center', width:50,sort: true}
|
||||
,{field:'task_name',width:300, title: '任务名称'}
|
||||
,{field:'description',title: '任务说明'}
|
||||
{field:'id', title: 'ID', align:'center', width:100,sort: true}
|
||||
,{field:'task_name', title: '任务名称'}
|
||||
,{field:'cron_spec',title: '时间表达式', width:150}
|
||||
,{field:'next_time', width:170,title: '下次执行时间'}
|
||||
,{field:'pre_time', title: '上次执行时间'}
|
||||
,{field:'execute_times', title: '次数'}
|
||||
,{field:'pre_time', title: '上次执行时间',width:170,}
|
||||
,{field:'execute_times', title: '次数',width:100}
|
||||
,{width:120, align:'center', title:'操作', toolbar: '#bar'}
|
||||
]]
|
||||
,id: 'listReload'
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
{checkbox: true, fixed: true}
|
||||
// ,{field:'id', title: 'ID', align:'center',sort: true, width:150}
|
||||
,{field:'task_id', title: '任务ID', align:'center',width:100}
|
||||
,{field:'server_name', title: '服务器', width:150}
|
||||
,{field:'server_name', title: '服务器', width:250}
|
||||
,{field:'start_time',title: '开始时间'}
|
||||
,{field:'process_time',width:100, title: '执行时间'}
|
||||
,{field:'output_size',title: '输出'}
|
||||
|
||||
Reference in New Issue
Block a user