v2版本正式上线测试版
18
V1/conf/app.conf
Normal file
@@ -0,0 +1,18 @@
|
||||
appname = PPGo_Job
|
||||
httpport = 8080
|
||||
runmode = dev
|
||||
|
||||
# 允许同时运行的任务数
|
||||
jobs.pool = 1000
|
||||
|
||||
# 站点名称
|
||||
site.name = 定时任务管理器
|
||||
|
||||
# 数据库配置
|
||||
db.host = 127.0.0.1
|
||||
db.user = root
|
||||
db.password = "123456"
|
||||
db.port = 3306
|
||||
db.name = ppgo_job
|
||||
db.prefix = pp_
|
||||
db.timezone = Asia/Shanghai
|
||||
128
V1/controllers/common.go
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* @Author: haodaquan
|
||||
* @Date: 2017-06-19 22:27:09
|
||||
* @Last Modified by: haodaquan
|
||||
* @Last Modified time: 2017-06-22 11:15:33
|
||||
*/
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
MSG_OK = 0
|
||||
MSG_ERR = -1
|
||||
)
|
||||
|
||||
type BaseController struct {
|
||||
beego.Controller
|
||||
controllerName string
|
||||
actionName string
|
||||
user *models.User
|
||||
userId int
|
||||
userName string
|
||||
pageSize int
|
||||
}
|
||||
|
||||
func (this *BaseController) Prepare() {
|
||||
this.pageSize = 20
|
||||
controllerName, actionName := this.GetControllerAndAction()
|
||||
this.controllerName = strings.ToLower(controllerName[0 : len(controllerName)-10])
|
||||
this.actionName = strings.ToLower(actionName)
|
||||
this.auth()
|
||||
|
||||
this.Data["version"] = beego.AppConfig.String("version")
|
||||
this.Data["siteName"] = beego.AppConfig.String("site.name")
|
||||
this.Data["curRoute"] = this.controllerName + "." + this.actionName
|
||||
this.Data["curController"] = this.controllerName
|
||||
this.Data["curAction"] = this.actionName
|
||||
this.Data["loginUserId"] = this.userId
|
||||
this.Data["loginUserName"] = this.userName
|
||||
this.Data["menuTag"] = this.controllerName
|
||||
}
|
||||
|
||||
//登录状态验证
|
||||
func (this *BaseController) auth() {
|
||||
arr := strings.Split(this.Ctx.GetCookie("auth"), "|")
|
||||
if len(arr) == 2 {
|
||||
idstr, password := arr[0], arr[1]
|
||||
userId, _ := strconv.Atoi(idstr)
|
||||
if userId > 0 {
|
||||
user, err := models.UserGetById(userId)
|
||||
if err == nil && password == libs.Md5([]byte(this.getClientIp()+"|"+user.Password+user.Salt)) {
|
||||
this.userId = user.Id
|
||||
this.userName = user.UserName
|
||||
this.user = user
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if this.userId == 0 && (this.controllerName != "main" ||
|
||||
(this.controllerName == "main" && this.actionName != "logout" && this.actionName != "login")) {
|
||||
this.redirect(beego.URLFor("MainController.Login"))
|
||||
}
|
||||
}
|
||||
|
||||
//渲染模版
|
||||
func (this *BaseController) display(tpl ...string) {
|
||||
var tplname string
|
||||
if len(tpl) > 0 {
|
||||
tplname = tpl[0] + ".html"
|
||||
} else {
|
||||
tplname = this.controllerName + "/" + this.actionName + ".html"
|
||||
}
|
||||
this.Layout = "public/layout.html"
|
||||
this.TplName = tplname
|
||||
}
|
||||
|
||||
// 重定向
|
||||
func (this *BaseController) redirect(url string) {
|
||||
this.Redirect(url, 302)
|
||||
this.StopRun()
|
||||
}
|
||||
|
||||
// 是否POST提交
|
||||
func (this *BaseController) isPost() bool {
|
||||
return this.Ctx.Request.Method == "POST"
|
||||
}
|
||||
|
||||
// 显示错误信息
|
||||
func (this *BaseController) showMsg(args ...string) {
|
||||
this.Data["message"] = args[0]
|
||||
redirect := this.Ctx.Request.Referer()
|
||||
if len(args) > 1 {
|
||||
redirect = args[1]
|
||||
}
|
||||
|
||||
this.Data["redirect"] = redirect
|
||||
this.Data["pageTitle"] = "系统提示"
|
||||
this.display("error/message")
|
||||
this.Render()
|
||||
this.StopRun()
|
||||
}
|
||||
|
||||
// 输出json
|
||||
func (this *BaseController) jsonResult(out interface{}) {
|
||||
this.Data["json"] = out
|
||||
this.ServeJSON()
|
||||
this.StopRun()
|
||||
}
|
||||
|
||||
func (this *BaseController) ajaxMsg(msg interface{}, msgno int) {
|
||||
out := make(map[string]interface{})
|
||||
out["status"] = msgno
|
||||
out["msg"] = msg
|
||||
|
||||
this.jsonResult(out)
|
||||
}
|
||||
|
||||
//获取用户IP地址
|
||||
func (this *BaseController) getClientIp() string {
|
||||
s := strings.Split(this.Ctx.Request.RemoteAddr, ":")
|
||||
return s[0]
|
||||
}
|
||||
137
V1/controllers/server.go
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* @Author: haodaquan
|
||||
* @Date: 2017-08-16 10:27:40
|
||||
* @Last Modified by: haodaquan
|
||||
* @Last Modified time: 2017-08-16 09:17:22
|
||||
*/
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ServerController struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
func (this *ServerController) List() {
|
||||
page, _ := this.GetInt("page")
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
result, count := models.TaskServerGetList(page, this.pageSize)
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
if(v.Type==0){
|
||||
row["type"] = "密码"
|
||||
}else {
|
||||
row["type"] = "密钥"
|
||||
}
|
||||
row["server_name"] = v.ServerName
|
||||
row["server_ip"] = v.ServerIp
|
||||
row["detail"] = v.Detail
|
||||
row["port"] = v.Port
|
||||
row["create_time"] = beego.Date(time.Unix(v.CreateTime, 0), "Y-m-d H:i:s")
|
||||
list[k] = row
|
||||
}
|
||||
this.Data["pageTitle"] = "服务器列表"
|
||||
this.Data["list"] = list
|
||||
this.Data["pageBar"] = libs.NewPager(page, int(count), this.pageSize, beego.URLFor("ServerController.List"), true).ToString()
|
||||
this.display()
|
||||
}
|
||||
|
||||
func (this *ServerController) Add() {
|
||||
if this.isPost() {
|
||||
server := new(models.TaskServer)
|
||||
server.ServerName = strings.TrimSpace(this.GetString("server_name"))
|
||||
server.ServerAccount = strings.TrimSpace(this.GetString("server_account"))
|
||||
server.ServerIp = strings.TrimSpace(this.GetString("server_ip"))
|
||||
server.Port,_= strconv.Atoi(this.GetString("port"))
|
||||
server.Type,_ = strconv.Atoi(this.GetString("type"))
|
||||
server.PrivateKeySrc = strings.TrimSpace(this.GetString("private_key_src"))
|
||||
server.PublicKeySrc = strings.TrimSpace(this.GetString("public_key_src"))
|
||||
server.Password = strings.TrimSpace(this.GetString("password"))
|
||||
server.Detail = strings.TrimSpace(this.GetString("detail"))
|
||||
server.CreateTime = time.Now().Unix()
|
||||
server.UpdateTime = time.Now().Unix()
|
||||
server.Status = 0
|
||||
_, err := models.TaskServerAdd(server)
|
||||
if err != nil {
|
||||
this.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
this.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
this.Data["pageTitle"] = "添加服务器"
|
||||
this.display()
|
||||
}
|
||||
|
||||
func (this *ServerController) Edit() {
|
||||
id, _ := this.GetInt("id")
|
||||
server, err := models.TaskServerGetById(id)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
if this.isPost() {
|
||||
server.ServerName = strings.TrimSpace(this.GetString("server_name"))
|
||||
server.ServerAccount = strings.TrimSpace(this.GetString("server_account"))
|
||||
server.ServerIp = strings.TrimSpace(this.GetString("server_ip"))
|
||||
server.Port,_ = strconv.Atoi(this.GetString("port"))
|
||||
server.Type,_ = strconv.Atoi(this.GetString("type"))
|
||||
server.Id,_ = strconv.Atoi(this.GetString("id"))
|
||||
server.PrivateKeySrc = strings.TrimSpace(this.GetString("private_key_src"))
|
||||
server.PublicKeySrc = strings.TrimSpace(this.GetString("public_key_src"))
|
||||
server.Password = strings.TrimSpace(this.GetString("password"))
|
||||
server.Detail = strings.TrimSpace(this.GetString("detail"))
|
||||
server.UpdateTime = time.Now().Unix()
|
||||
server.Status = 0
|
||||
err := server.Update()
|
||||
if err != nil {
|
||||
this.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
this.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
this.Data["pageTitle"] = "编辑服务器"
|
||||
this.Data["server"] = server
|
||||
this.display()
|
||||
}
|
||||
|
||||
//TODO删除更新
|
||||
func (this *ServerController) Batch() {
|
||||
action := this.GetString("action")
|
||||
ids := this.GetStrings("ids")
|
||||
if len(ids) < 1 {
|
||||
this.ajaxMsg("请选择要操作的项目", MSG_ERR)
|
||||
}
|
||||
|
||||
for _, v := range ids {
|
||||
id, _ := strconv.Atoi(v)
|
||||
if id < 1 {
|
||||
continue
|
||||
}
|
||||
switch action {
|
||||
case "delete":
|
||||
//查询服务器是否被占用
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "server_id", id)
|
||||
_, count := models.TaskGetList(1, 1000, filters...)
|
||||
if count > 0 {
|
||||
this.ajaxMsg("请先解除该服务器的任务占用", MSG_ERR)
|
||||
}else{
|
||||
models.TaskServerDelById(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
395
V1/controllers/task.go
Normal file
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* @Author: haodaquan
|
||||
* @Date: 2017-06-21 10:22:29
|
||||
* @Last Modified by: haodaquan
|
||||
* @Last Modified time: 2017-06-23 11:04:54
|
||||
*/
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
crons "github.com/george518/PPGo_Job/crons"
|
||||
"github.com/george518/PPGo_Job/jobs"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
)
|
||||
|
||||
type TaskController struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
// 任务列表
|
||||
func (this *TaskController) List() {
|
||||
page, _ := this.GetInt("page")
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
groupId, _ := this.GetInt("groupid")
|
||||
if groupId > 0 {
|
||||
this.Ctx.SetCookie("groupid", strconv.Itoa(groupId)+"|job")
|
||||
} else {
|
||||
arr := strings.Split(this.Ctx.GetCookie("groupid"), "|")
|
||||
groupId, _ = strconv.Atoi(arr[0])
|
||||
}
|
||||
|
||||
filters := make([]interface{}, 0)
|
||||
if groupId > 0 && groupId != 99 {
|
||||
filters = append(filters, "group_id", groupId)
|
||||
}
|
||||
result, count := models.TaskGetList(page, this.pageSize, filters...)
|
||||
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
|
||||
// 分组列表
|
||||
groups, _ := models.TaskGroupGetList(1, 100)
|
||||
groups_map := make(map[int]string)
|
||||
for _, gname := range groups {
|
||||
groups_map[gname.Id] = gname.GroupName
|
||||
}
|
||||
|
||||
//服务器列表
|
||||
servers, _ := models.TaskServerGetList(1, 100)
|
||||
|
||||
server_map := make(map[int]string)
|
||||
for _, sname := range servers {
|
||||
server_map[sname.Id] = sname.ServerName
|
||||
}
|
||||
server_map[0] = "本地"
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["name"] = v.TaskName
|
||||
row["cron_spec"] = v.CronSpec
|
||||
row["status"] = v.Status
|
||||
row["description"] = v.Description
|
||||
row["group_id"] = v.GroupId
|
||||
row["group_name"] = groups_map[v.GroupId]
|
||||
row["server_name"] = server_map[v.ServerId]
|
||||
row["is_odd"] = k % 2
|
||||
|
||||
e := jobs.GetEntryById(v.Id)
|
||||
if e != nil {
|
||||
row["next_time"] = beego.Date(e.Next, "Y-m-d H:i:s")
|
||||
row["prev_time"] = "-"
|
||||
if e.Prev.Unix() > 0 {
|
||||
row["prev_time"] = beego.Date(e.Prev, "Y-m-d H:i:s")
|
||||
} else if v.PrevTime > 0 {
|
||||
row["prev_time"] = beego.Date(time.Unix(v.PrevTime, 0), "Y-m-d H:i:s")
|
||||
}
|
||||
row["running"] = 1
|
||||
} else {
|
||||
row["next_time"] = "-"
|
||||
if v.PrevTime > 0 {
|
||||
row["prev_time"] = beego.Date(time.Unix(v.PrevTime, 0), "Y-m-d H:i:s")
|
||||
} else {
|
||||
row["prev_time"] = "-"
|
||||
}
|
||||
row["running"] = 0
|
||||
}
|
||||
list[k] = row
|
||||
}
|
||||
|
||||
this.Data["pageTitle"] = "任务列表"
|
||||
this.Data["list"] = list
|
||||
this.Data["groups"] = groups
|
||||
this.Data["groupid"] = groupId
|
||||
this.Data["pageBar"] = libs.NewPager(page, int(count), this.pageSize, beego.URLFor("TaskController.List", "groupid", groupId), true).ToString()
|
||||
this.display()
|
||||
}
|
||||
|
||||
// 添加任务
|
||||
func (this *TaskController) Add() {
|
||||
|
||||
if this.isPost() {
|
||||
task := new(models.Task)
|
||||
task.UserId = this.userId
|
||||
task.GroupId, _ = this.GetInt("group_id")
|
||||
task.TaskName = strings.TrimSpace(this.GetString("task_name"))
|
||||
task.Description = strings.TrimSpace(this.GetString("description"))
|
||||
task.Concurrent, _ = this.GetInt("concurrent")
|
||||
task.ServerId, _ = this.GetInt("server_id")
|
||||
task.CronSpec = strings.TrimSpace(this.GetString("cron_spec"))
|
||||
task.Command = strings.TrimSpace(this.GetString("command"))
|
||||
task.Timeout, _ = this.GetInt("timeout")
|
||||
|
||||
if task.TaskName == "" || task.CronSpec == "" || task.Command == "" {
|
||||
this.ajaxMsg("请填写完整信息", MSG_ERR)
|
||||
}
|
||||
if _, err := crons.Parse(task.CronSpec); err != nil {
|
||||
this.ajaxMsg("cron表达式无效", MSG_ERR)
|
||||
}
|
||||
if _, err := models.TaskAdd(task); err != nil {
|
||||
this.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
|
||||
this.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
// 分组列表
|
||||
groups, _ := models.TaskGroupGetList(1, 100)
|
||||
this.Data["groups"] = groups
|
||||
//服务器分组
|
||||
servers, _ := models.TaskServerGetList(1, 100)
|
||||
this.Data["servers"] = servers
|
||||
this.Data["pageTitle"] = "添加任务"
|
||||
this.display()
|
||||
}
|
||||
|
||||
// 编辑任务
|
||||
func (this *TaskController) Edit() {
|
||||
id, _ := this.GetInt("id")
|
||||
|
||||
task, err := models.TaskGetById(id)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
if task.Status != 0 {
|
||||
this.ajaxMsg("激活状态无法编辑任务,请先暂停任务", MSG_ERR)
|
||||
}
|
||||
|
||||
if this.isPost() {
|
||||
task.TaskName = strings.TrimSpace(this.GetString("task_name"))
|
||||
task.Description = strings.TrimSpace(this.GetString("description"))
|
||||
task.GroupId, _ = this.GetInt("group_id")
|
||||
task.Concurrent, _ = this.GetInt("concurrent")
|
||||
task.ServerId, _ = this.GetInt("server_id")
|
||||
task.CronSpec = strings.TrimSpace(this.GetString("cron_spec"))
|
||||
task.Command = strings.TrimSpace(this.GetString("command"))
|
||||
task.Timeout, _ = this.GetInt("timeout")
|
||||
if task.TaskName == "" || task.CronSpec == "" || task.Command == "" {
|
||||
this.ajaxMsg("请填写完整信息", MSG_ERR)
|
||||
}
|
||||
if _, err := crons.Parse(task.CronSpec); err != nil {
|
||||
this.ajaxMsg("cron表达式无效", MSG_ERR)
|
||||
}
|
||||
if err := task.Update(); err != nil {
|
||||
this.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
|
||||
this.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
// 分组列表
|
||||
groups, _ := models.TaskGroupGetList(1, 100)
|
||||
this.Data["groups"] = groups
|
||||
//服务器分组
|
||||
servers, _ := models.TaskServerGetList(1, 100)
|
||||
this.Data["servers"] = servers
|
||||
|
||||
this.Data["task"] = task
|
||||
this.Data["pageTitle"] = "编辑任务"
|
||||
this.display()
|
||||
}
|
||||
|
||||
//复制任务
|
||||
func (this *TaskController) Copy() {
|
||||
|
||||
id, _ := this.GetInt("id")
|
||||
task, err := models.TaskGetById(id)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
// 分组列表
|
||||
groups, _ := models.TaskGroupGetList(1, 100)
|
||||
this.Data["groups"] = groups
|
||||
//服务器分组
|
||||
servers, _ := models.TaskServerGetList(1, 100)
|
||||
this.Data["servers"] = servers
|
||||
|
||||
this.Data["task"] = task
|
||||
this.Data["pageTitle"] = "复制任务"
|
||||
this.display()
|
||||
}
|
||||
|
||||
// 任务执行日志列表
|
||||
func (this *TaskController) Logs() {
|
||||
taskId, _ := this.GetInt("id")
|
||||
page, _ := this.GetInt("page")
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
task, err := models.TaskGetById(taskId)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
result, count := models.TaskLogGetList(page, this.pageSize, "task_id", task.Id)
|
||||
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["start_time"] = beego.Date(time.Unix(v.CreateTime, 0), "Y-m-d H:i:s")
|
||||
row["process_time"] = float64(v.ProcessTime) / 1000
|
||||
row["ouput_size"] = libs.SizeFormat(float64(len(v.Output)))
|
||||
row["status"] = v.Status
|
||||
list[k] = row
|
||||
}
|
||||
|
||||
this.Data["pageTitle"] = "任务执行日志"
|
||||
this.Data["list"] = list
|
||||
this.Data["task"] = task
|
||||
this.Data["pageBar"] = libs.NewPager(page, int(count), this.pageSize, beego.URLFor("TaskController.Logs", "id", taskId), true).ToString()
|
||||
this.display()
|
||||
}
|
||||
|
||||
// 查看日志详情
|
||||
func (this *TaskController) ViewLog() {
|
||||
id, _ := this.GetInt("id")
|
||||
|
||||
taskLog, err := models.TaskLogGetById(id)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
task, err := models.TaskGetById(taskLog.TaskId)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
data := make(map[string]interface{})
|
||||
data["id"] = taskLog.Id
|
||||
data["output"] = taskLog.Output
|
||||
data["error"] = taskLog.Error
|
||||
data["start_time"] = beego.Date(time.Unix(taskLog.CreateTime, 0), "Y-m-d H:i:s")
|
||||
data["process_time"] = float64(taskLog.ProcessTime) / 1000
|
||||
data["ouput_size"] = libs.SizeFormat(float64(len(taskLog.Output)))
|
||||
data["status"] = taskLog.Status
|
||||
|
||||
this.Data["task"] = task
|
||||
this.Data["data"] = data
|
||||
this.Data["pageTitle"] = "查看日志"
|
||||
this.display()
|
||||
}
|
||||
|
||||
// 批量操作日志
|
||||
func (this *TaskController) LogBatch() {
|
||||
action := this.GetString("action")
|
||||
ids := this.GetStrings("ids")
|
||||
if len(ids) < 1 {
|
||||
this.ajaxMsg("请选择要操作的项目", MSG_ERR)
|
||||
}
|
||||
for _, v := range ids {
|
||||
id, _ := strconv.Atoi(v)
|
||||
if id < 1 {
|
||||
continue
|
||||
}
|
||||
switch action {
|
||||
case "delete":
|
||||
models.TaskLogDelById(id)
|
||||
}
|
||||
}
|
||||
|
||||
this.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
// 批量操作
|
||||
func (this *TaskController) Batch() {
|
||||
action := this.GetString("action")
|
||||
ids := this.GetStrings("ids")
|
||||
if len(ids) < 1 {
|
||||
this.ajaxMsg("请选择要操作的项目", MSG_ERR)
|
||||
}
|
||||
|
||||
for _, v := range ids {
|
||||
id, _ := strconv.Atoi(v)
|
||||
if id < 1 {
|
||||
continue
|
||||
}
|
||||
switch action {
|
||||
case "active":
|
||||
if task, err := models.TaskGetById(id); err == nil {
|
||||
job, err := jobs.NewJobFromTask(task)
|
||||
if err == nil {
|
||||
jobs.AddJob(task.CronSpec, job)
|
||||
task.Status = 1
|
||||
task.Update()
|
||||
}
|
||||
}
|
||||
case "pause":
|
||||
jobs.RemoveJob(id)
|
||||
|
||||
if task, err := models.TaskGetById(id); err == nil {
|
||||
task.Status = 0
|
||||
task.Update()
|
||||
}
|
||||
|
||||
case "delete":
|
||||
models.TaskDel(id)
|
||||
models.TaskLogDelByTaskId(id)
|
||||
jobs.RemoveJob(id)
|
||||
}
|
||||
}
|
||||
|
||||
this.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
// 启动任务
|
||||
func (this *TaskController) Start() {
|
||||
id, _ := this.GetInt("id")
|
||||
|
||||
task, err := models.TaskGetById(id)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
job, err := jobs.NewJobFromTask(task)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
if jobs.AddJob(task.CronSpec, job) {
|
||||
task.Status = 1
|
||||
task.Update()
|
||||
}
|
||||
|
||||
refer := this.Ctx.Request.Referer()
|
||||
if refer == "" {
|
||||
refer = beego.URLFor("TaskController.List")
|
||||
}
|
||||
this.redirect(refer)
|
||||
}
|
||||
|
||||
// 暂停任务
|
||||
func (this *TaskController) Pause() {
|
||||
id, _ := this.GetInt("id")
|
||||
|
||||
task, err := models.TaskGetById(id)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
jobs.RemoveJob(id)
|
||||
task.Status = 0
|
||||
task.Update()
|
||||
|
||||
refer := this.Ctx.Request.Referer()
|
||||
if refer == "" {
|
||||
refer = beego.URLFor("TaskController.List")
|
||||
}
|
||||
this.redirect(refer)
|
||||
}
|
||||
|
||||
// 立即执行
|
||||
func (this *TaskController) Run() {
|
||||
id, _ := this.GetInt("id")
|
||||
|
||||
task, err := models.TaskGetById(id)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
job, err := jobs.NewJobFromTask(task)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
job.Run()
|
||||
this.redirect(beego.URLFor("TaskController.ViewLog", "id", job.GetLogId()))
|
||||
}
|
||||
21
V1/crons/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
Copyright (C) 2012 Rob Figueiredo
|
||||
All Rights Reserved.
|
||||
|
||||
MIT LICENSE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
2
V1/crons/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
[](http://godoc.org/github.com/robfig/cron)
|
||||
[](https://travis-ci.org/robfig/cron)
|
||||
27
V1/crons/constantdelay.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package cron
|
||||
|
||||
import "time"
|
||||
|
||||
// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes".
|
||||
// It does not support jobs more frequent than once a second.
|
||||
type ConstantDelaySchedule struct {
|
||||
Delay time.Duration
|
||||
}
|
||||
|
||||
// Every returns a crontab Schedule that activates once every duration.
|
||||
// Delays of less than a second are not supported (will round up to 1 second).
|
||||
// Any fields less than a Second are truncated.
|
||||
func Every(duration time.Duration) ConstantDelaySchedule {
|
||||
if duration < time.Second {
|
||||
duration = time.Second
|
||||
}
|
||||
return ConstantDelaySchedule{
|
||||
Delay: duration - time.Duration(duration.Nanoseconds())%time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// Next returns the next time this should be run.
|
||||
// This rounds so that the next activation time will be on the second.
|
||||
func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time {
|
||||
return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond)
|
||||
}
|
||||
54
V1/crons/constantdelay_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package cron
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConstantDelayNext(t *testing.T) {
|
||||
tests := []struct {
|
||||
time string
|
||||
delay time.Duration
|
||||
expected string
|
||||
}{
|
||||
// Simple cases
|
||||
{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
|
||||
{"Mon Jul 9 14:59 2012", 15 * time.Minute, "Mon Jul 9 15:14 2012"},
|
||||
{"Mon Jul 9 14:59:59 2012", 15 * time.Minute, "Mon Jul 9 15:14:59 2012"},
|
||||
|
||||
// Wrap around hours
|
||||
{"Mon Jul 9 15:45 2012", 35 * time.Minute, "Mon Jul 9 16:20 2012"},
|
||||
|
||||
// Wrap around days
|
||||
{"Mon Jul 9 23:46 2012", 14 * time.Minute, "Tue Jul 10 00:00 2012"},
|
||||
{"Mon Jul 9 23:45 2012", 35 * time.Minute, "Tue Jul 10 00:20 2012"},
|
||||
{"Mon Jul 9 23:35:51 2012", 44*time.Minute + 24*time.Second, "Tue Jul 10 00:20:15 2012"},
|
||||
{"Mon Jul 9 23:35:51 2012", 25*time.Hour + 44*time.Minute + 24*time.Second, "Thu Jul 11 01:20:15 2012"},
|
||||
|
||||
// Wrap around months
|
||||
{"Mon Jul 9 23:35 2012", 91*24*time.Hour + 25*time.Minute, "Thu Oct 9 00:00 2012"},
|
||||
|
||||
// Wrap around minute, hour, day, month, and year
|
||||
{"Mon Dec 31 23:59:45 2012", 15 * time.Second, "Tue Jan 1 00:00:00 2013"},
|
||||
|
||||
// Round to nearest second on the delay
|
||||
{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
|
||||
|
||||
// Round up to 1 second if the duration is less.
|
||||
{"Mon Jul 9 14:45:00 2012", 15 * time.Millisecond, "Mon Jul 9 14:45:01 2012"},
|
||||
|
||||
// Round to nearest second when calculating the next time.
|
||||
{"Mon Jul 9 14:45:00.005 2012", 15 * time.Minute, "Mon Jul 9 15:00 2012"},
|
||||
|
||||
// Round to nearest second for both.
|
||||
{"Mon Jul 9 14:45:00.005 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
|
||||
}
|
||||
|
||||
for _, c := range tests {
|
||||
actual := Every(c.delay).Next(getTime(c.time))
|
||||
expected := getTime(c.expected)
|
||||
if actual != expected {
|
||||
t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.delay, expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
222
V1/crons/cron.go
Normal file
@@ -0,0 +1,222 @@
|
||||
// This library implements a cron spec parser and runner. See the README for
|
||||
// more details.
|
||||
package cron
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 删除job的检查函数,返回true则删除
|
||||
type RemoveCheckFunc func(e *Entry) bool
|
||||
|
||||
// Cron keeps track of any number of entries, invoking the associated func as
|
||||
// specified by the schedule. It may be started, stopped, and the entries may
|
||||
// be inspected while running.
|
||||
// Cron保持任意数量的条目的轨道,调用相关的func时间表指定。它可以被启动,停止和条目,可运行的同时进行检查。
|
||||
type Cron struct {
|
||||
entries []*Entry //任务
|
||||
stop chan struct{} //停止的通道
|
||||
add chan *Entry //添加新任务的方式
|
||||
remove chan RemoveCheckFunc
|
||||
snapshot chan []*Entry //请求获取任务快照的方式
|
||||
running bool
|
||||
}
|
||||
|
||||
// Job is an interface for submitted cron jobs.
|
||||
type Job interface {
|
||||
Run()
|
||||
}
|
||||
|
||||
// The Schedule describes a job's duty cycle.
|
||||
type Schedule interface {
|
||||
// Return the next activation time, later than the given time.
|
||||
// Next is invoked initially, and then each time the job is run.
|
||||
Next(time.Time) time.Time
|
||||
}
|
||||
|
||||
// Entry consists of a schedule and the func to execute on that schedule.
|
||||
type Entry struct {
|
||||
// The schedule on which this job should be run.
|
||||
Schedule Schedule
|
||||
|
||||
// The next time the job will run. This is the zero time if Cron has not been
|
||||
// started or this entry's schedule is unsatisfiable
|
||||
Next time.Time
|
||||
|
||||
// The last time this job was run. This is the zero time if the job has never
|
||||
// been run.
|
||||
Prev time.Time
|
||||
|
||||
// The Job to run.
|
||||
Job Job
|
||||
}
|
||||
|
||||
// byTime is a wrapper for sorting the entry array by time
|
||||
// (with zero time at the end).
|
||||
type byTime []*Entry
|
||||
|
||||
func (s byTime) Len() int { return len(s) }
|
||||
func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s byTime) Less(i, j int) bool {
|
||||
// Two zero times should return false.
|
||||
// Otherwise, zero is "greater" than any other time.
|
||||
// (To sort it at the end of the list.)
|
||||
if s[i].Next.IsZero() {
|
||||
return false
|
||||
}
|
||||
if s[j].Next.IsZero() {
|
||||
return true
|
||||
}
|
||||
return s[i].Next.Before(s[j].Next)
|
||||
}
|
||||
|
||||
// New returns a new Cron job runner.
|
||||
func New() *Cron {
|
||||
return &Cron{
|
||||
entries: nil,
|
||||
add: make(chan *Entry),
|
||||
remove: make(chan RemoveCheckFunc),
|
||||
stop: make(chan struct{}),
|
||||
snapshot: make(chan []*Entry),
|
||||
running: false,
|
||||
}
|
||||
}
|
||||
|
||||
// A wrapper that turns a func() into a cron.Job
|
||||
type FuncJob func()
|
||||
|
||||
func (f FuncJob) Run() { f() }
|
||||
|
||||
// AddFunc adds a func to the Cron to be run on the given schedule.
|
||||
func (c *Cron) AddFunc(spec string, cmd func()) error {
|
||||
return c.AddJob(spec, FuncJob(cmd))
|
||||
}
|
||||
|
||||
// AddFunc adds a Job to the Cron to be run on the given schedule.
|
||||
func (c *Cron) AddJob(spec string, cmd Job) error {
|
||||
schedule, err := Parse(spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Schedule(schedule, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cron) RemoveJob(cb RemoveCheckFunc) {
|
||||
c.remove <- cb
|
||||
}
|
||||
|
||||
// Schedule adds a Job to the Cron to be run on the given schedule.
|
||||
func (c *Cron) Schedule(schedule Schedule, cmd Job) {
|
||||
entry := &Entry{
|
||||
Schedule: schedule,
|
||||
Job: cmd,
|
||||
}
|
||||
if !c.running {
|
||||
c.entries = append(c.entries, entry)
|
||||
return
|
||||
}
|
||||
|
||||
c.add <- entry
|
||||
}
|
||||
|
||||
// Entries returns a snapshot of the cron entries.
|
||||
func (c *Cron) Entries() []*Entry {
|
||||
if c.running {
|
||||
c.snapshot <- nil
|
||||
x := <-c.snapshot
|
||||
return x
|
||||
}
|
||||
return c.entrySnapshot()
|
||||
}
|
||||
|
||||
// Start the cron scheduler in its own go-routine.
|
||||
func (c *Cron) Start() {
|
||||
c.running = true
|
||||
|
||||
go c.run()
|
||||
}
|
||||
|
||||
// Run the scheduler.. this is private just due to the need to synchronize
|
||||
// access to the 'running' state variable.
|
||||
func (c *Cron) run() {
|
||||
// Figure out the next activation times for each entry.
|
||||
now := time.Now().Local()
|
||||
for _, entry := range c.entries {
|
||||
entry.Next = entry.Schedule.Next(now)
|
||||
}
|
||||
|
||||
for {
|
||||
// Determine the next entry to run.
|
||||
sort.Sort(byTime(c.entries))
|
||||
|
||||
var effective time.Time
|
||||
if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
|
||||
// If there are no entries yet, just sleep - it still handles new entries
|
||||
// and stop requests.
|
||||
effective = now.AddDate(10, 0, 0)
|
||||
} else {
|
||||
effective = c.entries[0].Next
|
||||
}
|
||||
|
||||
select {
|
||||
case now = <-time.After(effective.Sub(now)):
|
||||
// Run every entry whose next time was this effective time.
|
||||
for _, e := range c.entries {
|
||||
if e.Next != effective {
|
||||
break
|
||||
}
|
||||
go e.Job.Run()
|
||||
e.Prev = e.Next
|
||||
e.Next = e.Schedule.Next(effective)
|
||||
}
|
||||
continue
|
||||
|
||||
case newEntry := <-c.add:
|
||||
c.entries = append(c.entries, newEntry)
|
||||
newEntry.Next = newEntry.Schedule.Next(now)
|
||||
|
||||
case cb := <-c.remove:
|
||||
newEntries := make([]*Entry, 0)
|
||||
for _, e := range c.entries {
|
||||
if !cb(e) {
|
||||
newEntries = append(newEntries, e)
|
||||
}
|
||||
}
|
||||
c.entries = newEntries
|
||||
|
||||
case <-c.snapshot:
|
||||
c.snapshot <- c.entrySnapshot()
|
||||
|
||||
case <-c.stop:
|
||||
return
|
||||
}
|
||||
|
||||
// 'now' should be updated after newEntry and snapshot cases.
|
||||
now = time.Now().Local()
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops the cron scheduler if it is running; otherwise it does nothing.
|
||||
func (c *Cron) Stop() {
|
||||
if !c.running {
|
||||
return
|
||||
}
|
||||
c.stop <- struct{}{}
|
||||
c.running = false
|
||||
}
|
||||
|
||||
// entrySnapshot returns a copy of the current cron entry list.
|
||||
func (c *Cron) entrySnapshot() []*Entry {
|
||||
entries := []*Entry{}
|
||||
for _, e := range c.entries {
|
||||
entries = append(entries, &Entry{
|
||||
Schedule: e.Schedule,
|
||||
Next: e.Next,
|
||||
Prev: e.Prev,
|
||||
Job: e.Job,
|
||||
})
|
||||
}
|
||||
return entries
|
||||
}
|
||||
262
V1/crons/cron_test.go
Normal file
@@ -0,0 +1,262 @@
|
||||
package cron
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Many tests schedule a job for every second, and then wait at most a second
|
||||
// for it to run. This amount is just slightly larger than 1 second to
|
||||
// compensate for a few milliseconds of runtime.
|
||||
const ONE_SECOND = 1*time.Second + 10*time.Millisecond
|
||||
|
||||
// Start and stop cron with no entries.
|
||||
func TestNoEntries(t *testing.T) {
|
||||
cron := New()
|
||||
cron.Start()
|
||||
|
||||
select {
|
||||
case <-time.After(ONE_SECOND):
|
||||
t.FailNow()
|
||||
case <-stop(cron):
|
||||
}
|
||||
}
|
||||
|
||||
// Start, stop, then add an entry. Verify entry doesn't run.
|
||||
func TestStopCausesJobsToNotRun(t *testing.T) {
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
cron := New()
|
||||
cron.Start()
|
||||
cron.Stop()
|
||||
cron.AddFunc("* * * * * ?", func() { wg.Done() })
|
||||
|
||||
select {
|
||||
case <-time.After(ONE_SECOND):
|
||||
// No job ran!
|
||||
case <-wait(wg):
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// Add a job, start cron, expect it runs.
|
||||
func TestAddBeforeRunning(t *testing.T) {
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
cron := New()
|
||||
cron.AddFunc("* * * * * ?", func() { wg.Done() })
|
||||
cron.Start()
|
||||
defer cron.Stop()
|
||||
|
||||
// Give cron 2 seconds to run our job (which is always activated).
|
||||
select {
|
||||
case <-time.After(ONE_SECOND):
|
||||
t.FailNow()
|
||||
case <-wait(wg):
|
||||
}
|
||||
}
|
||||
|
||||
// Start cron, add a job, expect it runs.
|
||||
func TestAddWhileRunning(t *testing.T) {
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
cron := New()
|
||||
cron.Start()
|
||||
defer cron.Stop()
|
||||
cron.AddFunc("* * * * * ?", func() { wg.Done() })
|
||||
|
||||
select {
|
||||
case <-time.After(ONE_SECOND):
|
||||
t.FailNow()
|
||||
case <-wait(wg):
|
||||
}
|
||||
}
|
||||
|
||||
// Test timing with Entries.
|
||||
func TestSnapshotEntries(t *testing.T) {
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
cron := New()
|
||||
cron.AddFunc("@every 2s", func() { wg.Done() })
|
||||
cron.Start()
|
||||
defer cron.Stop()
|
||||
|
||||
// Cron should fire in 2 seconds. After 1 second, call Entries.
|
||||
select {
|
||||
case <-time.After(ONE_SECOND):
|
||||
cron.Entries()
|
||||
}
|
||||
|
||||
// Even though Entries was called, the cron should fire at the 2 second mark.
|
||||
select {
|
||||
case <-time.After(ONE_SECOND):
|
||||
t.FailNow()
|
||||
case <-wait(wg):
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Test that the entries are correctly sorted.
|
||||
// Add a bunch of long-in-the-future entries, and an immediate entry, and ensure
|
||||
// that the immediate entry runs immediately.
|
||||
// Also: Test that multiple jobs run in the same instant.
|
||||
func TestMultipleEntries(t *testing.T) {
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
|
||||
cron := New()
|
||||
cron.AddFunc("0 0 0 1 1 ?", func() {})
|
||||
cron.AddFunc("* * * * * ?", func() { wg.Done() })
|
||||
cron.AddFunc("0 0 0 31 12 ?", func() {})
|
||||
cron.AddFunc("* * * * * ?", func() { wg.Done() })
|
||||
|
||||
cron.Start()
|
||||
defer cron.Stop()
|
||||
|
||||
select {
|
||||
case <-time.After(ONE_SECOND):
|
||||
t.FailNow()
|
||||
case <-wait(wg):
|
||||
}
|
||||
}
|
||||
|
||||
// Test running the same job twice.
|
||||
func TestRunningJobTwice(t *testing.T) {
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
|
||||
cron := New()
|
||||
cron.AddFunc("0 0 0 1 1 ?", func() {})
|
||||
cron.AddFunc("0 0 0 31 12 ?", func() {})
|
||||
cron.AddFunc("* * * * * ?", func() { wg.Done() })
|
||||
|
||||
cron.Start()
|
||||
defer cron.Stop()
|
||||
|
||||
select {
|
||||
case <-time.After(2 * ONE_SECOND):
|
||||
t.FailNow()
|
||||
case <-wait(wg):
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunningMultipleSchedules(t *testing.T) {
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
|
||||
cron := New()
|
||||
cron.AddFunc("0 0 0 1 1 ?", func() {})
|
||||
cron.AddFunc("0 0 0 31 12 ?", func() {})
|
||||
cron.AddFunc("* * * * * ?", func() { wg.Done() })
|
||||
cron.Schedule(Every(time.Minute), FuncJob(func() {}))
|
||||
cron.Schedule(Every(time.Second), FuncJob(func() { wg.Done() }))
|
||||
cron.Schedule(Every(time.Hour), FuncJob(func() {}))
|
||||
|
||||
cron.Start()
|
||||
defer cron.Stop()
|
||||
|
||||
select {
|
||||
case <-time.After(2 * ONE_SECOND):
|
||||
t.FailNow()
|
||||
case <-wait(wg):
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the cron is run in the local time zone (as opposed to UTC).
|
||||
func TestLocalTimezone(t *testing.T) {
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
now := time.Now().Local()
|
||||
spec := fmt.Sprintf("%d %d %d %d %d ?",
|
||||
now.Second()+1, now.Minute(), now.Hour(), now.Day(), now.Month())
|
||||
|
||||
cron := New()
|
||||
cron.AddFunc(spec, func() { wg.Done() })
|
||||
cron.Start()
|
||||
defer cron.Stop()
|
||||
|
||||
select {
|
||||
case <-time.After(ONE_SECOND):
|
||||
t.FailNow()
|
||||
case <-wait(wg):
|
||||
}
|
||||
}
|
||||
|
||||
// Test that calling stop before start silently returns without
|
||||
// blocking the stop channel.
|
||||
func TestStopWithoutStart(t *testing.T) {
|
||||
cron := New()
|
||||
cron.Stop()
|
||||
}
|
||||
|
||||
type testJob struct {
|
||||
wg *sync.WaitGroup
|
||||
name string
|
||||
}
|
||||
|
||||
func (t testJob) Run() {
|
||||
t.wg.Done()
|
||||
}
|
||||
|
||||
// Simple test using Runnables.
|
||||
func TestJob(t *testing.T) {
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
cron := New()
|
||||
cron.AddJob("0 0 0 30 Feb ?", testJob{wg, "job0"})
|
||||
cron.AddJob("0 0 0 1 1 ?", testJob{wg, "job1"})
|
||||
cron.AddJob("* * * * * ?", testJob{wg, "job2"})
|
||||
cron.AddJob("1 0 0 1 1 ?", testJob{wg, "job3"})
|
||||
cron.Schedule(Every(5*time.Second+5*time.Nanosecond), testJob{wg, "job4"})
|
||||
cron.Schedule(Every(5*time.Minute), testJob{wg, "job5"})
|
||||
|
||||
cron.Start()
|
||||
defer cron.Stop()
|
||||
|
||||
select {
|
||||
case <-time.After(ONE_SECOND):
|
||||
t.FailNow()
|
||||
case <-wait(wg):
|
||||
}
|
||||
|
||||
// Ensure the entries are in the right order.
|
||||
expecteds := []string{"job2", "job4", "job5", "job1", "job3", "job0"}
|
||||
|
||||
var actuals []string
|
||||
for _, entry := range cron.Entries() {
|
||||
actuals = append(actuals, entry.Job.(testJob).name)
|
||||
}
|
||||
|
||||
for i, expected := range expecteds {
|
||||
if actuals[i] != expected {
|
||||
t.Errorf("Jobs not in the right order. (expected) %s != %s (actual)", expecteds, actuals)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func wait(wg *sync.WaitGroup) chan bool {
|
||||
ch := make(chan bool)
|
||||
go func() {
|
||||
wg.Wait()
|
||||
ch <- true
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func stop(cron *Cron) chan bool {
|
||||
ch := make(chan bool)
|
||||
go func() {
|
||||
cron.Stop()
|
||||
ch <- true
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
129
V1/crons/doc.go
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
Package cron implements a cron spec parser and job runner.
|
||||
|
||||
Usage
|
||||
|
||||
Callers may register Funcs to be invoked on a given schedule. Cron will run
|
||||
them in their own goroutines.
|
||||
|
||||
c := cron.New()
|
||||
c.AddFunc("0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })
|
||||
c.AddFunc("@hourly", func() { fmt.Println("Every hour") })
|
||||
c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty") })
|
||||
c.Start()
|
||||
..
|
||||
// Funcs are invoked in their own goroutine, asynchronously.
|
||||
...
|
||||
// Funcs may also be added to a running Cron
|
||||
c.AddFunc("@daily", func() { fmt.Println("Every day") })
|
||||
..
|
||||
// Inspect the cron job entries' next and previous run times.
|
||||
inspect(c.Entries())
|
||||
..
|
||||
c.Stop() // Stop the scheduler (does not stop any jobs already running).
|
||||
|
||||
CRON Expression Format
|
||||
|
||||
A cron expression represents a set of times, using 6 space-separated fields.
|
||||
|
||||
Field name | Mandatory? | Allowed values | Allowed special characters
|
||||
---------- | ---------- | -------------- | --------------------------
|
||||
Seconds | Yes | 0-59 | * / , -
|
||||
Minutes | Yes | 0-59 | * / , -
|
||||
Hours | Yes | 0-23 | * / , -
|
||||
Day of month | Yes | 1-31 | * / , - ?
|
||||
Month | Yes | 1-12 or JAN-DEC | * / , -
|
||||
Day of week | Yes | 0-6 or SUN-SAT | * / , - ?
|
||||
|
||||
Note: Month and Day-of-week field values are case insensitive. "SUN", "Sun",
|
||||
and "sun" are equally accepted.
|
||||
|
||||
Special Characters
|
||||
|
||||
Asterisk ( * )
|
||||
|
||||
The asterisk indicates that the cron expression will match for all values of the
|
||||
field; e.g., using an asterisk in the 5th field (month) would indicate every
|
||||
month.
|
||||
|
||||
Slash ( / )
|
||||
|
||||
Slashes are used to describe increments of ranges. For example 3-59/15 in the
|
||||
1st field (minutes) would indicate the 3rd minute of the hour and every 15
|
||||
minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...",
|
||||
that is, an increment over the largest possible range of the field. The form
|
||||
"N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the
|
||||
increment until the end of that specific range. It does not wrap around.
|
||||
|
||||
Comma ( , )
|
||||
|
||||
Commas are used to separate items of a list. For example, using "MON,WED,FRI" in
|
||||
the 5th field (day of week) would mean Mondays, Wednesdays and Fridays.
|
||||
|
||||
Hyphen ( - )
|
||||
|
||||
Hyphens are used to define ranges. For example, 9-17 would indicate every
|
||||
hour between 9am and 5pm inclusive.
|
||||
|
||||
Question mark ( ? )
|
||||
|
||||
Question mark may be used instead of '*' for leaving either day-of-month or
|
||||
day-of-week blank.
|
||||
|
||||
Predefined schedules
|
||||
|
||||
You may use one of several pre-defined schedules in place of a cron expression.
|
||||
|
||||
Entry | Description | Equivalent To
|
||||
----- | ----------- | -------------
|
||||
@yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 0 1 1 *
|
||||
@monthly | Run once a month, midnight, first of month | 0 0 0 1 * *
|
||||
@weekly | Run once a week, midnight on Sunday | 0 0 0 * * 0
|
||||
@daily (or @midnight) | Run once a day, midnight | 0 0 0 * * *
|
||||
@hourly | Run once an hour, beginning of hour | 0 0 * * * *
|
||||
|
||||
Intervals
|
||||
|
||||
You may also schedule a job to execute at fixed intervals. This is supported by
|
||||
formatting the cron spec like this:
|
||||
|
||||
@every <duration>
|
||||
|
||||
where "duration" is a string accepted by time.ParseDuration
|
||||
(http://golang.org/pkg/time/#ParseDuration).
|
||||
|
||||
For example, "@every 1h30m10s" would indicate a schedule that activates every
|
||||
1 hour, 30 minutes, 10 seconds.
|
||||
|
||||
Note: The interval does not take the job runtime into account. For example,
|
||||
if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes,
|
||||
it will have only 2 minutes of idle time between each run.
|
||||
|
||||
Time zones
|
||||
|
||||
All interpretation and scheduling is done in the machine's local time zone (as
|
||||
provided by the Go time package (http://www.golang.org/pkg/time).
|
||||
|
||||
Be aware that jobs scheduled during daylight-savings leap-ahead transitions will
|
||||
not be run!
|
||||
|
||||
Thread safety
|
||||
|
||||
Since the Cron service runs concurrently with the calling code, some amount of
|
||||
care must be taken to ensure proper synchronization.
|
||||
|
||||
All cron methods are designed to be correctly synchronized as long as the caller
|
||||
ensures that invocations have a clear happens-before ordering between them.
|
||||
|
||||
Implementation
|
||||
|
||||
Cron entries are stored in an array, sorted by their next activation time. Cron
|
||||
sleeps until the next job is due to be run.
|
||||
|
||||
Upon waking:
|
||||
- it runs each entry that is active on that second
|
||||
- it calculates the next run times for the jobs that were run
|
||||
- it re-sorts the array of entries by next activation time.
|
||||
- it goes to sleep until the soonest job.
|
||||
*/
|
||||
package cron
|
||||
231
V1/crons/parser.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package cron
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Parse returns a new crontab schedule representing the given spec.
|
||||
// It returns a descriptive error if the spec is not valid.
|
||||
//
|
||||
// It accepts
|
||||
// - Full crontab specs, e.g. "* * * * * ?"
|
||||
// - Descriptors, e.g. "@midnight", "@every 1h30m"
|
||||
func Parse(spec string) (_ Schedule, err error) {
|
||||
// Convert panics into errors
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
err = fmt.Errorf("%v", recovered)
|
||||
}
|
||||
}()
|
||||
|
||||
if spec[0] == '@' {
|
||||
return parseDescriptor(spec), nil
|
||||
}
|
||||
|
||||
// Split on whitespace. We require 5 or 6 fields.
|
||||
// (second) (minute) (hour) (day of month) (month) (day of week, optional)
|
||||
fields := strings.Fields(spec)
|
||||
if len(fields) != 5 && len(fields) != 6 {
|
||||
log.Panicf("Expected 5 or 6 fields, found %d: %s", len(fields), spec)
|
||||
}
|
||||
|
||||
// If a sixth field is not provided (DayOfWeek), then it is equivalent to star.
|
||||
if len(fields) == 5 {
|
||||
fields = append(fields, "*")
|
||||
}
|
||||
|
||||
schedule := &SpecSchedule{
|
||||
Second: getField(fields[0], seconds),
|
||||
Minute: getField(fields[1], minutes),
|
||||
Hour: getField(fields[2], hours),
|
||||
Dom: getField(fields[3], dom),
|
||||
Month: getField(fields[4], months),
|
||||
Dow: getField(fields[5], dow),
|
||||
}
|
||||
|
||||
return schedule, nil
|
||||
}
|
||||
|
||||
// getField returns an Int with the bits set representing all of the times that
|
||||
// the field represents. A "field" is a comma-separated list of "ranges".
|
||||
func getField(field string, r bounds) uint64 {
|
||||
// list = range {"," range}
|
||||
var bits uint64
|
||||
ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })
|
||||
for _, expr := range ranges {
|
||||
bits |= getRange(expr, r)
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
||||
// getRange returns the bits indicated by the given expression:
|
||||
// number | number "-" number [ "/" number ]
|
||||
func getRange(expr string, r bounds) uint64 {
|
||||
|
||||
var (
|
||||
start, end, step uint
|
||||
rangeAndStep = strings.Split(expr, "/")
|
||||
lowAndHigh = strings.Split(rangeAndStep[0], "-")
|
||||
singleDigit = len(lowAndHigh) == 1
|
||||
)
|
||||
|
||||
var extra_star uint64
|
||||
if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {
|
||||
start = r.min
|
||||
end = r.max
|
||||
extra_star = starBit
|
||||
} else {
|
||||
start = parseIntOrName(lowAndHigh[0], r.names)
|
||||
switch len(lowAndHigh) {
|
||||
case 1:
|
||||
end = start
|
||||
case 2:
|
||||
end = parseIntOrName(lowAndHigh[1], r.names)
|
||||
default:
|
||||
log.Panicf("Too many hyphens: %s", expr)
|
||||
}
|
||||
}
|
||||
|
||||
switch len(rangeAndStep) {
|
||||
case 1:
|
||||
step = 1
|
||||
case 2:
|
||||
step = mustParseInt(rangeAndStep[1])
|
||||
|
||||
// Special handling: "N/step" means "N-max/step".
|
||||
if singleDigit {
|
||||
end = r.max
|
||||
}
|
||||
default:
|
||||
log.Panicf("Too many slashes: %s", expr)
|
||||
}
|
||||
|
||||
if start < r.min {
|
||||
log.Panicf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr)
|
||||
}
|
||||
if end > r.max {
|
||||
log.Panicf("End of range (%d) above maximum (%d): %s", end, r.max, expr)
|
||||
}
|
||||
if start > end {
|
||||
log.Panicf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr)
|
||||
}
|
||||
|
||||
return getBits(start, end, step) | extra_star
|
||||
}
|
||||
|
||||
// parseIntOrName returns the (possibly-named) integer contained in expr.
|
||||
func parseIntOrName(expr string, names map[string]uint) uint {
|
||||
if names != nil {
|
||||
if namedInt, ok := names[strings.ToLower(expr)]; ok {
|
||||
return namedInt
|
||||
}
|
||||
}
|
||||
return mustParseInt(expr)
|
||||
}
|
||||
|
||||
// mustParseInt parses the given expression as an int or panics.
|
||||
func mustParseInt(expr string) uint {
|
||||
num, err := strconv.Atoi(expr)
|
||||
if err != nil {
|
||||
log.Panicf("Failed to parse int from %s: %s", expr, err)
|
||||
}
|
||||
if num < 0 {
|
||||
log.Panicf("Negative number (%d) not allowed: %s", num, expr)
|
||||
}
|
||||
|
||||
return uint(num)
|
||||
}
|
||||
|
||||
// getBits sets all bits in the range [min, max], modulo the given step size.
|
||||
func getBits(min, max, step uint) uint64 {
|
||||
var bits uint64
|
||||
|
||||
// If step is 1, use shifts.
|
||||
if step == 1 {
|
||||
return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)
|
||||
}
|
||||
|
||||
// Else, use a simple loop.
|
||||
for i := min; i <= max; i += step {
|
||||
bits |= 1 << i
|
||||
}
|
||||
return bits
|
||||
}
|
||||
|
||||
// all returns all bits within the given bounds. (plus the star bit)
|
||||
func all(r bounds) uint64 {
|
||||
return getBits(r.min, r.max, 1) | starBit
|
||||
}
|
||||
|
||||
// parseDescriptor returns a pre-defined schedule for the expression, or panics
|
||||
// if none matches.
|
||||
func parseDescriptor(spec string) Schedule {
|
||||
switch spec {
|
||||
case "@yearly", "@annually":
|
||||
return &SpecSchedule{
|
||||
Second: 1 << seconds.min,
|
||||
Minute: 1 << minutes.min,
|
||||
Hour: 1 << hours.min,
|
||||
Dom: 1 << dom.min,
|
||||
Month: 1 << months.min,
|
||||
Dow: all(dow),
|
||||
}
|
||||
|
||||
case "@monthly":
|
||||
return &SpecSchedule{
|
||||
Second: 1 << seconds.min,
|
||||
Minute: 1 << minutes.min,
|
||||
Hour: 1 << hours.min,
|
||||
Dom: 1 << dom.min,
|
||||
Month: all(months),
|
||||
Dow: all(dow),
|
||||
}
|
||||
|
||||
case "@weekly":
|
||||
return &SpecSchedule{
|
||||
Second: 1 << seconds.min,
|
||||
Minute: 1 << minutes.min,
|
||||
Hour: 1 << hours.min,
|
||||
Dom: all(dom),
|
||||
Month: all(months),
|
||||
Dow: 1 << dow.min,
|
||||
}
|
||||
|
||||
case "@daily", "@midnight":
|
||||
return &SpecSchedule{
|
||||
Second: 1 << seconds.min,
|
||||
Minute: 1 << minutes.min,
|
||||
Hour: 1 << hours.min,
|
||||
Dom: all(dom),
|
||||
Month: all(months),
|
||||
Dow: all(dow),
|
||||
}
|
||||
|
||||
case "@hourly":
|
||||
return &SpecSchedule{
|
||||
Second: 1 << seconds.min,
|
||||
Minute: 1 << minutes.min,
|
||||
Hour: all(hours),
|
||||
Dom: all(dom),
|
||||
Month: all(months),
|
||||
Dow: all(dow),
|
||||
}
|
||||
}
|
||||
|
||||
const every = "@every "
|
||||
if strings.HasPrefix(spec, every) {
|
||||
duration, err := time.ParseDuration(spec[len(every):])
|
||||
if err != nil {
|
||||
log.Panicf("Failed to parse duration %s: %s", spec, err)
|
||||
}
|
||||
return Every(duration)
|
||||
}
|
||||
|
||||
log.Panicf("Unrecognized descriptor: %s", spec)
|
||||
return nil
|
||||
}
|
||||
117
V1/crons/parser_test.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package cron
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRange(t *testing.T) {
|
||||
ranges := []struct {
|
||||
expr string
|
||||
min, max uint
|
||||
expected uint64
|
||||
}{
|
||||
{"5", 0, 7, 1 << 5},
|
||||
{"0", 0, 7, 1 << 0},
|
||||
{"7", 0, 7, 1 << 7},
|
||||
|
||||
{"5-5", 0, 7, 1 << 5},
|
||||
{"5-6", 0, 7, 1<<5 | 1<<6},
|
||||
{"5-7", 0, 7, 1<<5 | 1<<6 | 1<<7},
|
||||
|
||||
{"5-6/2", 0, 7, 1 << 5},
|
||||
{"5-7/2", 0, 7, 1<<5 | 1<<7},
|
||||
{"5-7/1", 0, 7, 1<<5 | 1<<6 | 1<<7},
|
||||
|
||||
{"*", 1, 3, 1<<1 | 1<<2 | 1<<3 | starBit},
|
||||
{"*/2", 1, 3, 1<<1 | 1<<3 | starBit},
|
||||
}
|
||||
|
||||
for _, c := range ranges {
|
||||
actual := getRange(c.expr, bounds{c.min, c.max, nil})
|
||||
if actual != c.expected {
|
||||
t.Errorf("%s => (expected) %d != %d (actual)", c.expr, c.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestField(t *testing.T) {
|
||||
fields := []struct {
|
||||
expr string
|
||||
min, max uint
|
||||
expected uint64
|
||||
}{
|
||||
{"5", 1, 7, 1 << 5},
|
||||
{"5,6", 1, 7, 1<<5 | 1<<6},
|
||||
{"5,6,7", 1, 7, 1<<5 | 1<<6 | 1<<7},
|
||||
{"1,5-7/2,3", 1, 7, 1<<1 | 1<<5 | 1<<7 | 1<<3},
|
||||
}
|
||||
|
||||
for _, c := range fields {
|
||||
actual := getField(c.expr, bounds{c.min, c.max, nil})
|
||||
if actual != c.expected {
|
||||
t.Errorf("%s => (expected) %d != %d (actual)", c.expr, c.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBits(t *testing.T) {
|
||||
allBits := []struct {
|
||||
r bounds
|
||||
expected uint64
|
||||
}{
|
||||
{minutes, 0xfffffffffffffff}, // 0-59: 60 ones
|
||||
{hours, 0xffffff}, // 0-23: 24 ones
|
||||
{dom, 0xfffffffe}, // 1-31: 31 ones, 1 zero
|
||||
{months, 0x1ffe}, // 1-12: 12 ones, 1 zero
|
||||
{dow, 0x7f}, // 0-6: 7 ones
|
||||
}
|
||||
|
||||
for _, c := range allBits {
|
||||
actual := all(c.r) // all() adds the starBit, so compensate for that..
|
||||
if c.expected|starBit != actual {
|
||||
t.Errorf("%d-%d/%d => (expected) %b != %b (actual)",
|
||||
c.r.min, c.r.max, 1, c.expected|starBit, actual)
|
||||
}
|
||||
}
|
||||
|
||||
bits := []struct {
|
||||
min, max, step uint
|
||||
expected uint64
|
||||
}{
|
||||
|
||||
{0, 0, 1, 0x1},
|
||||
{1, 1, 1, 0x2},
|
||||
{1, 5, 2, 0x2a}, // 101010
|
||||
{1, 4, 2, 0xa}, // 1010
|
||||
}
|
||||
|
||||
for _, c := range bits {
|
||||
actual := getBits(c.min, c.max, c.step)
|
||||
if c.expected != actual {
|
||||
t.Errorf("%d-%d/%d => (expected) %b != %b (actual)",
|
||||
c.min, c.max, c.step, c.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecSchedule(t *testing.T) {
|
||||
entries := []struct {
|
||||
expr string
|
||||
expected Schedule
|
||||
}{
|
||||
{"* 5 * * * *", &SpecSchedule{all(seconds), 1 << 5, all(hours), all(dom), all(months), all(dow)}},
|
||||
{"@every 5m", ConstantDelaySchedule{time.Duration(5) * time.Minute}},
|
||||
}
|
||||
|
||||
for _, c := range entries {
|
||||
actual, err := Parse(c.expr)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, c.expected) {
|
||||
t.Errorf("%s => (expected) %b != %b (actual)", c.expr, c.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
159
V1/crons/spec.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package cron
|
||||
|
||||
import "time"
|
||||
|
||||
// SpecSchedule specifies a duty cycle (to the second granularity), based on a
|
||||
// traditional crontab specification. It is computed initially and stored as bit sets.
|
||||
type SpecSchedule struct {
|
||||
Second, Minute, Hour, Dom, Month, Dow uint64
|
||||
}
|
||||
|
||||
// bounds provides a range of acceptable values (plus a map of name to value).
|
||||
type bounds struct {
|
||||
min, max uint
|
||||
names map[string]uint
|
||||
}
|
||||
|
||||
// The bounds for each field.
|
||||
var (
|
||||
seconds = bounds{0, 59, nil}
|
||||
minutes = bounds{0, 59, nil}
|
||||
hours = bounds{0, 23, nil}
|
||||
dom = bounds{1, 31, nil}
|
||||
months = bounds{1, 12, map[string]uint{
|
||||
"jan": 1,
|
||||
"feb": 2,
|
||||
"mar": 3,
|
||||
"apr": 4,
|
||||
"may": 5,
|
||||
"jun": 6,
|
||||
"jul": 7,
|
||||
"aug": 8,
|
||||
"sep": 9,
|
||||
"oct": 10,
|
||||
"nov": 11,
|
||||
"dec": 12,
|
||||
}}
|
||||
dow = bounds{0, 6, map[string]uint{
|
||||
"sun": 0,
|
||||
"mon": 1,
|
||||
"tue": 2,
|
||||
"wed": 3,
|
||||
"thu": 4,
|
||||
"fri": 5,
|
||||
"sat": 6,
|
||||
}}
|
||||
)
|
||||
|
||||
const (
|
||||
// Set the top bit if a star was included in the expression.
|
||||
starBit = 1 << 63
|
||||
)
|
||||
|
||||
// Next returns the next time this schedule is activated, greater than the given
|
||||
// time. If no time can be found to satisfy the schedule, return the zero time.
|
||||
func (s *SpecSchedule) Next(t time.Time) time.Time {
|
||||
// General approach:
|
||||
// For Month, Day, Hour, Minute, Second:
|
||||
// Check if the time value matches. If yes, continue to the next field.
|
||||
// If the field doesn't match the schedule, then increment the field until it matches.
|
||||
// While incrementing the field, a wrap-around brings it back to the beginning
|
||||
// of the field list (since it is necessary to re-verify previous field
|
||||
// values)
|
||||
|
||||
// Start at the earliest possible time (the upcoming second).
|
||||
t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond)
|
||||
|
||||
// This flag indicates whether a field has been incremented.
|
||||
added := false
|
||||
|
||||
// If no time is found within five years, return zero.
|
||||
yearLimit := t.Year() + 5
|
||||
|
||||
WRAP:
|
||||
if t.Year() > yearLimit {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// Find the first applicable month.
|
||||
// If it's this month, then do nothing.
|
||||
for 1<<uint(t.Month())&s.Month == 0 {
|
||||
// If we have to add a month, reset the other parts to 0.
|
||||
if !added {
|
||||
added = true
|
||||
// Otherwise, set the date at the beginning (since the current time is irrelevant).
|
||||
t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
t = t.AddDate(0, 1, 0)
|
||||
|
||||
// Wrapped around.
|
||||
if t.Month() == time.January {
|
||||
goto WRAP
|
||||
}
|
||||
}
|
||||
|
||||
// Now get a day in that month.
|
||||
for !dayMatches(s, t) {
|
||||
if !added {
|
||||
added = true
|
||||
t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
t = t.AddDate(0, 0, 1)
|
||||
|
||||
if t.Day() == 1 {
|
||||
goto WRAP
|
||||
}
|
||||
}
|
||||
|
||||
for 1<<uint(t.Hour())&s.Hour == 0 {
|
||||
if !added {
|
||||
added = true
|
||||
t = t.Truncate(time.Hour)
|
||||
}
|
||||
t = t.Add(1 * time.Hour)
|
||||
|
||||
if t.Hour() == 0 {
|
||||
goto WRAP
|
||||
}
|
||||
}
|
||||
|
||||
for 1<<uint(t.Minute())&s.Minute == 0 {
|
||||
if !added {
|
||||
added = true
|
||||
t = t.Truncate(time.Minute)
|
||||
}
|
||||
t = t.Add(1 * time.Minute)
|
||||
|
||||
if t.Minute() == 0 {
|
||||
goto WRAP
|
||||
}
|
||||
}
|
||||
|
||||
for 1<<uint(t.Second())&s.Second == 0 {
|
||||
if !added {
|
||||
added = true
|
||||
t = t.Truncate(time.Second)
|
||||
}
|
||||
t = t.Add(1 * time.Second)
|
||||
|
||||
if t.Second() == 0 {
|
||||
goto WRAP
|
||||
}
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// dayMatches returns true if the schedule's day-of-week and day-of-month
|
||||
// restrictions are satisfied by the given time.
|
||||
func dayMatches(s *SpecSchedule, t time.Time) bool {
|
||||
var (
|
||||
domMatch bool = 1<<uint(t.Day())&s.Dom > 0
|
||||
dowMatch bool = 1<<uint(t.Weekday())&s.Dow > 0
|
||||
)
|
||||
|
||||
if s.Dom&starBit > 0 || s.Dow&starBit > 0 {
|
||||
return domMatch && dowMatch
|
||||
}
|
||||
return domMatch || dowMatch
|
||||
}
|
||||
204
V1/crons/spec_test.go
Normal file
@@ -0,0 +1,204 @@
|
||||
package cron
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestActivation(t *testing.T) {
|
||||
tests := []struct {
|
||||
time, spec string
|
||||
expected bool
|
||||
}{
|
||||
// Every fifteen minutes.
|
||||
{"Mon Jul 9 15:00 2012", "0 0/15 * * *", true},
|
||||
{"Mon Jul 9 15:45 2012", "0 0/15 * * *", true},
|
||||
{"Mon Jul 9 15:40 2012", "0 0/15 * * *", false},
|
||||
|
||||
// Every fifteen minutes, starting at 5 minutes.
|
||||
{"Mon Jul 9 15:05 2012", "0 5/15 * * *", true},
|
||||
{"Mon Jul 9 15:20 2012", "0 5/15 * * *", true},
|
||||
{"Mon Jul 9 15:50 2012", "0 5/15 * * *", true},
|
||||
|
||||
// Named months
|
||||
{"Sun Jul 15 15:00 2012", "0 0/15 * * Jul", true},
|
||||
{"Sun Jul 15 15:00 2012", "0 0/15 * * Jun", false},
|
||||
|
||||
// Everything set.
|
||||
{"Sun Jul 15 08:30 2012", "0 30 08 ? Jul Sun", true},
|
||||
{"Sun Jul 15 08:30 2012", "0 30 08 15 Jul ?", true},
|
||||
{"Mon Jul 16 08:30 2012", "0 30 08 ? Jul Sun", false},
|
||||
{"Mon Jul 16 08:30 2012", "0 30 08 15 Jul ?", false},
|
||||
|
||||
// Predefined schedules
|
||||
{"Mon Jul 9 15:00 2012", "@hourly", true},
|
||||
{"Mon Jul 9 15:04 2012", "@hourly", false},
|
||||
{"Mon Jul 9 15:00 2012", "@daily", false},
|
||||
{"Mon Jul 9 00:00 2012", "@daily", true},
|
||||
{"Mon Jul 9 00:00 2012", "@weekly", false},
|
||||
{"Sun Jul 8 00:00 2012", "@weekly", true},
|
||||
{"Sun Jul 8 01:00 2012", "@weekly", false},
|
||||
{"Sun Jul 8 00:00 2012", "@monthly", false},
|
||||
{"Sun Jul 1 00:00 2012", "@monthly", true},
|
||||
|
||||
// Test interaction of DOW and DOM.
|
||||
// If both are specified, then only one needs to match.
|
||||
{"Sun Jul 15 00:00 2012", "0 * * 1,15 * Sun", true},
|
||||
{"Fri Jun 15 00:00 2012", "0 * * 1,15 * Sun", true},
|
||||
{"Wed Aug 1 00:00 2012", "0 * * 1,15 * Sun", true},
|
||||
|
||||
// However, if one has a star, then both need to match.
|
||||
{"Sun Jul 15 00:00 2012", "0 * * * * Mon", false},
|
||||
{"Sun Jul 15 00:00 2012", "0 * * */10 * Sun", false},
|
||||
{"Mon Jul 9 00:00 2012", "0 * * 1,15 * *", false},
|
||||
{"Sun Jul 15 00:00 2012", "0 * * 1,15 * *", true},
|
||||
{"Sun Jul 15 00:00 2012", "0 * * */2 * Sun", true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
sched, err := Parse(test.spec)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
actual := sched.Next(getTime(test.time).Add(-1 * time.Second))
|
||||
expected := getTime(test.time)
|
||||
if test.expected && expected != actual || !test.expected && expected == actual {
|
||||
t.Errorf("Fail evaluating %s on %s: (expected) %s != %s (actual)",
|
||||
test.spec, test.time, expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNext(t *testing.T) {
|
||||
runs := []struct {
|
||||
time, spec string
|
||||
expected string
|
||||
}{
|
||||
// Simple cases
|
||||
{"Mon Jul 9 14:45 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
|
||||
{"Mon Jul 9 14:59 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
|
||||
{"Mon Jul 9 14:59:59 2012", "0 0/15 * * *", "Mon Jul 9 15:00 2012"},
|
||||
|
||||
// Wrap around hours
|
||||
{"Mon Jul 9 15:45 2012", "0 20-35/15 * * *", "Mon Jul 9 16:20 2012"},
|
||||
|
||||
// Wrap around days
|
||||
{"Mon Jul 9 23:46 2012", "0 */15 * * *", "Tue Jul 10 00:00 2012"},
|
||||
{"Mon Jul 9 23:45 2012", "0 20-35/15 * * *", "Tue Jul 10 00:20 2012"},
|
||||
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * * *", "Tue Jul 10 00:20:15 2012"},
|
||||
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 * *", "Tue Jul 10 01:20:15 2012"},
|
||||
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 10-12 * *", "Tue Jul 10 10:20:15 2012"},
|
||||
|
||||
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 */2 * *", "Thu Jul 11 01:20:15 2012"},
|
||||
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 * *", "Wed Jul 10 00:20:15 2012"},
|
||||
{"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 Jul *", "Wed Jul 10 00:20:15 2012"},
|
||||
|
||||
// Wrap around months
|
||||
{"Mon Jul 9 23:35 2012", "0 0 0 9 Apr-Oct ?", "Thu Aug 9 00:00 2012"},
|
||||
{"Mon Jul 9 23:35 2012", "0 0 0 */5 Apr,Aug,Oct Mon", "Mon Aug 6 00:00 2012"},
|
||||
{"Mon Jul 9 23:35 2012", "0 0 0 */5 Oct Mon", "Mon Oct 1 00:00 2012"},
|
||||
|
||||
// Wrap around years
|
||||
{"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon", "Mon Feb 4 00:00 2013"},
|
||||
{"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon/2", "Fri Feb 1 00:00 2013"},
|
||||
|
||||
// Wrap around minute, hour, day, month, and year
|
||||
{"Mon Dec 31 23:59:45 2012", "0 * * * * *", "Tue Jan 1 00:00:00 2013"},
|
||||
|
||||
// Leap year
|
||||
{"Mon Jul 9 23:35 2012", "0 0 0 29 Feb ?", "Mon Feb 29 00:00 2016"},
|
||||
|
||||
// Daylight savings time 2am EST (-5) -> 3am EDT (-4)
|
||||
{"2012-03-11T00:00:00-0500", "0 30 2 11 Mar ?", "2013-03-11T02:30:00-0400"},
|
||||
|
||||
// hourly job
|
||||
{"2012-03-11T00:00:00-0500", "0 0 * * * ?", "2012-03-11T01:00:00-0500"},
|
||||
{"2012-03-11T01:00:00-0500", "0 0 * * * ?", "2012-03-11T03:00:00-0400"},
|
||||
{"2012-03-11T03:00:00-0400", "0 0 * * * ?", "2012-03-11T04:00:00-0400"},
|
||||
{"2012-03-11T04:00:00-0400", "0 0 * * * ?", "2012-03-11T05:00:00-0400"},
|
||||
|
||||
// 1am nightly job
|
||||
{"2012-03-11T00:00:00-0500", "0 0 1 * * ?", "2012-03-11T01:00:00-0500"},
|
||||
{"2012-03-11T01:00:00-0500", "0 0 1 * * ?", "2012-03-12T01:00:00-0400"},
|
||||
|
||||
// 2am nightly job (skipped)
|
||||
{"2012-03-11T00:00:00-0500", "0 0 2 * * ?", "2012-03-12T02:00:00-0400"},
|
||||
|
||||
// Daylight savings time 2am EDT (-4) => 1am EST (-5)
|
||||
{"2012-11-04T00:00:00-0400", "0 30 2 04 Nov ?", "2012-11-04T02:30:00-0500"},
|
||||
{"2012-11-04T01:45:00-0400", "0 30 1 04 Nov ?", "2012-11-04T01:30:00-0500"},
|
||||
|
||||
// hourly job
|
||||
{"2012-11-04T00:00:00-0400", "0 0 * * * ?", "2012-11-04T01:00:00-0400"},
|
||||
{"2012-11-04T01:00:00-0400", "0 0 * * * ?", "2012-11-04T01:00:00-0500"},
|
||||
{"2012-11-04T01:00:00-0500", "0 0 * * * ?", "2012-11-04T02:00:00-0500"},
|
||||
|
||||
// 1am nightly job (runs twice)
|
||||
{"2012-11-04T00:00:00-0400", "0 0 1 * * ?", "2012-11-04T01:00:00-0400"},
|
||||
{"2012-11-04T01:00:00-0400", "0 0 1 * * ?", "2012-11-04T01:00:00-0500"},
|
||||
{"2012-11-04T01:00:00-0500", "0 0 1 * * ?", "2012-11-05T01:00:00-0500"},
|
||||
|
||||
// 2am nightly job
|
||||
{"2012-11-04T00:00:00-0400", "0 0 2 * * ?", "2012-11-04T02:00:00-0500"},
|
||||
{"2012-11-04T02:00:00-0500", "0 0 2 * * ?", "2012-11-05T02:00:00-0500"},
|
||||
|
||||
// 3am nightly job
|
||||
{"2012-11-04T00:00:00-0400", "0 0 3 * * ?", "2012-11-04T03:00:00-0500"},
|
||||
{"2012-11-04T03:00:00-0500", "0 0 3 * * ?", "2012-11-05T03:00:00-0500"},
|
||||
|
||||
// Unsatisfiable
|
||||
{"Mon Jul 9 23:35 2012", "0 0 0 30 Feb ?", ""},
|
||||
{"Mon Jul 9 23:35 2012", "0 0 0 31 Apr ?", ""},
|
||||
}
|
||||
|
||||
for _, c := range runs {
|
||||
sched, err := Parse(c.spec)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
actual := sched.Next(getTime(c.time))
|
||||
expected := getTime(c.expected)
|
||||
if !actual.Equal(expected) {
|
||||
t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.spec, expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
invalidSpecs := []string{
|
||||
"xyz",
|
||||
"60 0 * * *",
|
||||
"0 60 * * *",
|
||||
"0 0 * * XYZ",
|
||||
}
|
||||
for _, spec := range invalidSpecs {
|
||||
_, err := Parse(spec)
|
||||
if err == nil {
|
||||
t.Error("expected an error parsing: ", spec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getTime(value string) time.Time {
|
||||
if value == "" {
|
||||
return time.Time{}
|
||||
}
|
||||
t, err := time.Parse("Mon Jan 2 15:04 2006", value)
|
||||
if err != nil {
|
||||
t, err = time.Parse("Mon Jan 2 15:04:05 2006", value)
|
||||
if err != nil {
|
||||
t, err = time.Parse("2006-01-02T15:04:05-0700", value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Daylight savings time tests require location
|
||||
if ny, err := time.LoadLocation("America/New_York"); err == nil {
|
||||
t = t.In(ny)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
74
V1/jobs/cron.go
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* @Author: haodaquan
|
||||
* @Date: 2017-06-21 12:54:47
|
||||
* @Last Modified by: haodaquan
|
||||
* @Last Modified time: 2017-06-23 11:04:25
|
||||
*/
|
||||
|
||||
package jobs
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/crons"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
mainCron *cron.Cron
|
||||
workPool chan bool
|
||||
lock sync.Mutex
|
||||
)
|
||||
|
||||
func init() {
|
||||
if size, _ := beego.AppConfig.Int("jobs.pool"); size > 0 {
|
||||
workPool = make(chan bool, size)
|
||||
}
|
||||
mainCron = cron.New()
|
||||
mainCron.Start()
|
||||
}
|
||||
|
||||
func AddJob(spec string, job *Job) bool {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
if GetEntryById(job.id) != nil {
|
||||
return false
|
||||
}
|
||||
err := mainCron.AddJob(spec, job)
|
||||
if err != nil {
|
||||
beego.Error("AddJob: ", err.Error())
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func RemoveJob(id int) {
|
||||
mainCron.RemoveJob(func(e *cron.Entry) bool {
|
||||
if v, ok := e.Job.(*Job); ok {
|
||||
if v.id == id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func GetEntryById(id int) *cron.Entry {
|
||||
entries := mainCron.Entries()
|
||||
for _, e := range entries {
|
||||
if v, ok := e.Job.(*Job); ok {
|
||||
if v.id == id {
|
||||
return e
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetEntries(size int) []*cron.Entry {
|
||||
ret := mainCron.Entries()
|
||||
if len(ret) > size {
|
||||
return ret[:size]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
51
V1/jobs/init.go
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* @Author: haodaquan
|
||||
* @Date: 2017-06-21 12:55:19
|
||||
* @Last Modified by: haodaquan
|
||||
* @Last Modified time: 2017-06-21 13:03:06
|
||||
*/
|
||||
|
||||
package jobs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
func InitJobs() {
|
||||
list, _ := models.TaskGetList(1, 1000000, "status", 1)
|
||||
for _, task := range list {
|
||||
job, err := NewJobFromTask(task)
|
||||
if err != nil {
|
||||
beego.Error("InitJobs:", err.Error())
|
||||
continue
|
||||
}
|
||||
AddJob(task.CronSpec, 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):
|
||||
beego.Warn(fmt.Sprintf("任务执行时间超过%d秒,进程将被强制杀掉: %d", int(timeout/time.Second), cmd.Process.Pid))
|
||||
go func() {
|
||||
<-done // 读出上面的goroutine数据,避免阻塞导致无法退出
|
||||
}()
|
||||
if err = cmd.Process.Kill(); err != nil {
|
||||
beego.Error(fmt.Sprintf("进程无法杀掉: %d, 错误信息: %s", cmd.Process.Pid, err))
|
||||
}
|
||||
return err, true
|
||||
case err = <-done:
|
||||
return err, false
|
||||
}
|
||||
}
|
||||
|
||||
272
V1/jobs/job.go
Normal file
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* @Author: haodaquan
|
||||
* @Date: 2017-06-21 12:56:08
|
||||
* @Last Modified by: haodaquan
|
||||
* @Last Modified time: 2017-06-21 13:05:57
|
||||
*/
|
||||
|
||||
package jobs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os/exec"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type Job struct {
|
||||
id int // 任务ID
|
||||
logId int64 // 日志记录ID
|
||||
name string // 任务名称
|
||||
task *models.Task // 任务对象
|
||||
runFunc func(time.Duration) (string, string, error, bool) // 执行函数
|
||||
status int // 任务状态,大于0表示正在执行中
|
||||
Concurrent bool // 同一个任务是否允许并行执行
|
||||
}
|
||||
|
||||
func NewJobFromTask(task *models.Task) (*Job, error) {
|
||||
if task.Id < 1 {
|
||||
return nil, fmt.Errorf("ToJob: 缺少id")
|
||||
}
|
||||
//本地程序执行
|
||||
if task.ServerId == 0 {
|
||||
job := NewCommandJob(task.Id, task.TaskName, task.Command)
|
||||
job.task = task
|
||||
job.Concurrent = task.Concurrent == 1
|
||||
return job, nil
|
||||
}
|
||||
|
||||
server, _ := models.TaskServerGetById(task.ServerId)
|
||||
if server.Type == 0 {
|
||||
//密码验证登录服务器
|
||||
job := RemoteCommandJobByPassword(task.Id, task.TaskName, task.Command, server)
|
||||
job.task = task
|
||||
job.Concurrent = task.Concurrent == 1
|
||||
return job, nil
|
||||
}
|
||||
|
||||
job := RemoteCommandJob(task.Id, task.TaskName, task.Command, server)
|
||||
job.task = task
|
||||
job.Concurrent = task.Concurrent == 1
|
||||
return job, nil
|
||||
|
||||
}
|
||||
|
||||
func NewCommandJob(id int, name string, command string) *Job {
|
||||
job := &Job{
|
||||
id: id,
|
||||
name: name,
|
||||
}
|
||||
job.runFunc = func(timeout time.Duration) (string, string, error, bool) {
|
||||
bufOut := new(bytes.Buffer)
|
||||
bufErr := new(bytes.Buffer)
|
||||
//cmd := exec.Command("/bin/bash", "-c", command)
|
||||
cmd := exec.Command("sh", "-c", command)
|
||||
cmd.Stdout = bufOut
|
||||
cmd.Stderr = bufErr
|
||||
cmd.Start()
|
||||
err, isTimeout := runCmdWithTimeout(cmd, timeout)
|
||||
|
||||
return bufOut.String(), bufErr.String(), err, isTimeout
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
//远程执行任务 密钥验证
|
||||
func RemoteCommandJob(id int, name string, command string, servers *models.TaskServer) *Job {
|
||||
job := &Job{
|
||||
id: id,
|
||||
name: name,
|
||||
}
|
||||
job.runFunc = func(timeout time.Duration) (string, string, error, bool) {
|
||||
|
||||
key, err := ioutil.ReadFile(servers.PrivateKeySrc)
|
||||
if err != nil {
|
||||
return "", "", err, false
|
||||
}
|
||||
// Create the Signer for this private key.
|
||||
signer, err := ssh.ParsePrivateKey(key)
|
||||
if err != nil {
|
||||
return "", "", err, false
|
||||
}
|
||||
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
|
||||
},
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
defer client.Close()
|
||||
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
return "", "", err, false
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// Once a Session is created, you can execute a single command on
|
||||
// the remote side using the Run method.
|
||||
|
||||
var b bytes.Buffer
|
||||
var c bytes.Buffer
|
||||
session.Stdout = &b
|
||||
session.Stderr = &c
|
||||
|
||||
//session.Output(command)
|
||||
if err := session.Run(command); err != nil {
|
||||
return "", "", err, false
|
||||
}
|
||||
isTimeout := false
|
||||
return b.String(), c.String(), err, isTimeout
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
func RemoteCommandJobByPassword(id int, name string, command string, servers *models.TaskServer) *Job {
|
||||
var (
|
||||
auth []ssh.AuthMethod
|
||||
addr string
|
||||
clientConfig *ssh.ClientConfig
|
||||
client *ssh.Client
|
||||
session *ssh.Session
|
||||
err error
|
||||
)
|
||||
|
||||
job := &Job{
|
||||
id: id,
|
||||
name: name,
|
||||
}
|
||||
job.runFunc = func(timeout time.Duration) (string, string, error, bool) {
|
||||
// get auth method
|
||||
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: 1000 * time.Second,
|
||||
}
|
||||
|
||||
// connet to ssh
|
||||
addr = fmt.Sprintf("%s:%d", servers.ServerIp, servers.Port)
|
||||
|
||||
if client, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
|
||||
return "", "", err, false
|
||||
}
|
||||
|
||||
defer client.Close()
|
||||
|
||||
// create session
|
||||
if session, err = client.NewSession(); err != nil {
|
||||
return "", "", err, false
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
var c bytes.Buffer
|
||||
session.Stdout = &b
|
||||
session.Stderr = &c
|
||||
|
||||
//session.Output(command)
|
||||
if err := session.Run(command); err != nil {
|
||||
return "", "", err, false
|
||||
}
|
||||
isTimeout := false
|
||||
return b.String(), c.String(), err, isTimeout
|
||||
}
|
||||
|
||||
return job
|
||||
}
|
||||
|
||||
func (j *Job) Status() int {
|
||||
return j.status
|
||||
}
|
||||
|
||||
func (j *Job) GetName() string {
|
||||
return j.name
|
||||
}
|
||||
|
||||
func (j *Job) GetId() int {
|
||||
return j.id
|
||||
}
|
||||
|
||||
func (j *Job) GetLogId() int64 {
|
||||
return j.logId
|
||||
}
|
||||
|
||||
func (j *Job) Run() {
|
||||
if !j.Concurrent && j.status > 0 {
|
||||
beego.Warn(fmt.Sprintf("任务[%d]上一次执行尚未结束,本次被忽略。", j.id))
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
beego.Error(err, "\n", string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
if workPool != nil {
|
||||
workPool <- true
|
||||
defer func() {
|
||||
<-workPool
|
||||
}()
|
||||
}
|
||||
|
||||
beego.Debug(fmt.Sprintf("开始执行任务: %d", j.id))
|
||||
|
||||
j.status++
|
||||
defer func() {
|
||||
j.status--
|
||||
}()
|
||||
|
||||
t := time.Now()
|
||||
timeout := time.Duration(time.Hour * 24)
|
||||
if j.task.Timeout > 0 {
|
||||
timeout = time.Second * time.Duration(j.task.Timeout)
|
||||
}
|
||||
cmdOut, cmdErr, err, isTimeout := j.runFunc(timeout)
|
||||
ut := time.Now().Sub(t) / time.Millisecond
|
||||
|
||||
// 插入日志
|
||||
log := new(models.TaskLog)
|
||||
log.TaskId = j.id
|
||||
log.Output = cmdOut
|
||||
log.Error = cmdErr
|
||||
log.ProcessTime = int(ut)
|
||||
log.CreateTime = t.Unix()
|
||||
|
||||
if 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.Status = models.TASK_ERROR
|
||||
log.Error = err.Error() + ":" + cmdErr
|
||||
}
|
||||
j.logId, _ = models.TaskLogAdd(log)
|
||||
|
||||
// 更新上次执行时间
|
||||
j.task.PrevTime = t.Unix()
|
||||
j.task.ExecuteTimes++
|
||||
j.task.Update("PrevTime", "ExecuteTimes")
|
||||
}
|
||||
37
V1/libs/string.go
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* @Author: haodaquan
|
||||
* @Date: 2017-06-20 10:01:39
|
||||
* @Last Modified by: haodaquan
|
||||
* @Last Modified time: 2017-06-20 10:02:07
|
||||
*/
|
||||
|
||||
package libs
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var emailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?")
|
||||
|
||||
func Md5(buf []byte) string {
|
||||
hash := md5.New()
|
||||
hash.Write(buf)
|
||||
return fmt.Sprintf("%x", hash.Sum(nil))
|
||||
}
|
||||
|
||||
func SizeFormat(size float64) string {
|
||||
units := []string{"Byte", "KB", "MB", "GB", "TB"}
|
||||
n := 0
|
||||
for size > 1024 {
|
||||
size /= 1024
|
||||
n += 1
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%.2f %s", size, units[n])
|
||||
}
|
||||
|
||||
func IsEmail(b []byte) bool {
|
||||
return emailPattern.Match(b)
|
||||
}
|
||||
23
V1/main.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
_ "github.com/george518/PPGo_Job/routers"
|
||||
"github.com/george518/PPGo_Job/jobs"
|
||||
)
|
||||
|
||||
const (
|
||||
VERSION = "1.0.0"
|
||||
)
|
||||
|
||||
func init() {
|
||||
//初始化数据模型
|
||||
models.Init()
|
||||
jobs.InitJobs()
|
||||
}
|
||||
|
||||
func main() {
|
||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||
beego.Run()
|
||||
}
|
||||
45
V1/models/init.go
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* @Author: haodaquan
|
||||
* @Date: 2017-06-20 09:44:44
|
||||
* @Last Modified by: haodaquan
|
||||
* @Last Modified time: 2017-06-21 12:21:37
|
||||
*/
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/orm"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/gpmgo/gopm/modules/log"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
dbhost := beego.AppConfig.String("db.host")
|
||||
dbport := beego.AppConfig.String("db.port")
|
||||
dbuser := beego.AppConfig.String("db.user")
|
||||
dbpassword := beego.AppConfig.String("db.password")
|
||||
dbname := beego.AppConfig.String("db.name")
|
||||
timezone := beego.AppConfig.String("db.timezone")
|
||||
if dbport == "" {
|
||||
dbport = "3306"
|
||||
}
|
||||
dsn := dbuser + ":" + dbpassword + "@tcp(" + dbhost + ":" + dbport + ")/" + dbname + "?charset=utf8"
|
||||
log.Fatal(dsn)
|
||||
|
||||
if timezone != "" {
|
||||
dsn = dsn + "&loc=" + url.QueryEscape(timezone)
|
||||
}
|
||||
orm.RegisterDataBase("default", "mysql", dsn)
|
||||
orm.RegisterModel(new(User), new(Task), new(TaskGroup), new(TaskLog), new(TaskServer))
|
||||
|
||||
if beego.AppConfig.String("runmode") == "dev" {
|
||||
orm.Debug = true
|
||||
}
|
||||
}
|
||||
|
||||
func TableName(name string) string {
|
||||
return beego.AppConfig.String("db.prefix") + name
|
||||
}
|
||||
107
V1/models/task.go
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* @Author: haodaquan
|
||||
* @Date: 2017-06-21 12:22:00
|
||||
* @Last Modified by: haodaquan
|
||||
* @Last Modified time: 2017-06-21 12:22:10
|
||||
*/
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/astaxie/beego/orm"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
TASK_SUCCESS = 0 // 任务执行成功
|
||||
TASK_ERROR = -1 // 任务执行出错
|
||||
TASK_TIMEOUT = -2 // 任务执行超时
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
Id int
|
||||
UserId int
|
||||
ServerId int
|
||||
GroupId int
|
||||
TaskName string
|
||||
TaskType int
|
||||
Description string
|
||||
CronSpec string
|
||||
Concurrent int
|
||||
Command string
|
||||
Status int
|
||||
Timeout int
|
||||
ExecuteTimes int
|
||||
PrevTime int64
|
||||
CreateTime int64
|
||||
}
|
||||
|
||||
func (t *Task) TableName() string {
|
||||
return TableName("task")
|
||||
}
|
||||
|
||||
func (t *Task) Update(fields ...string) error {
|
||||
if _, err := orm.NewOrm().Update(t, fields...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TaskAdd(task *Task) (int64, error) {
|
||||
if task.TaskName == "" {
|
||||
return 0, fmt.Errorf("TaskName字段不能为空")
|
||||
}
|
||||
|
||||
if task.CronSpec == "" {
|
||||
return 0, fmt.Errorf("CronSpec字段不能为空")
|
||||
}
|
||||
if task.Command == "" {
|
||||
return 0, fmt.Errorf("Command字段不能为空")
|
||||
}
|
||||
if task.CreateTime == 0 {
|
||||
task.CreateTime = time.Now().Unix()
|
||||
}
|
||||
return orm.NewOrm().Insert(task)
|
||||
}
|
||||
|
||||
func TaskGetList(page, pageSize int, filters ...interface{}) ([]*Task, int64) {
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
tasks := make([]*Task, 0)
|
||||
|
||||
query := orm.NewOrm().QueryTable(TableName("task"))
|
||||
if len(filters) > 0 {
|
||||
l := len(filters)
|
||||
for k := 0; k < l; k += 2 {
|
||||
query = query.Filter(filters[k].(string), filters[k+1])
|
||||
}
|
||||
}
|
||||
total, _ := query.Count()
|
||||
query.OrderBy("-id").Limit(pageSize, offset).All(&tasks)
|
||||
|
||||
return tasks, total
|
||||
}
|
||||
|
||||
func TaskResetGroupId(groupId int) (int64, error) {
|
||||
return orm.NewOrm().QueryTable(TableName("task")).Filter("group_id", groupId).Update(orm.Params{
|
||||
"group_id": 0,
|
||||
})
|
||||
}
|
||||
|
||||
func TaskGetById(id int) (*Task, error) {
|
||||
task := &Task{
|
||||
Id: id,
|
||||
}
|
||||
|
||||
err := orm.NewOrm().Read(task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func TaskDel(id int) error {
|
||||
_, err := orm.NewOrm().QueryTable(TableName("task")).Filter("id", id).Delete()
|
||||
return err
|
||||
}
|
||||
69
V1/models/task_group.go
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* @Author: haodaquan
|
||||
* @Date: 2017-06-21 12:22:37
|
||||
* @Last Modified by: haodaquan
|
||||
* @Last Modified time: 2017-06-21 12:22:55
|
||||
*/
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/astaxie/beego/orm"
|
||||
)
|
||||
|
||||
type TaskGroup struct {
|
||||
Id int
|
||||
UserId int
|
||||
GroupName string
|
||||
Description string
|
||||
CreateTime int64
|
||||
}
|
||||
|
||||
func (t *TaskGroup) TableName() string {
|
||||
return TableName("task_group")
|
||||
}
|
||||
|
||||
func (t *TaskGroup) Update(fields ...string) error {
|
||||
if t.GroupName == "" {
|
||||
return fmt.Errorf("组名不能为空")
|
||||
}
|
||||
if _, err := orm.NewOrm().Update(t, fields...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TaskGroupAdd(obj *TaskGroup) (int64, error) {
|
||||
if obj.GroupName == "" {
|
||||
return 0, fmt.Errorf("组名不能为空")
|
||||
}
|
||||
return orm.NewOrm().Insert(obj)
|
||||
}
|
||||
|
||||
func TaskGroupGetById(id int) (*TaskGroup, error) {
|
||||
obj := &TaskGroup{
|
||||
Id: id,
|
||||
}
|
||||
|
||||
err := orm.NewOrm().Read(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func TaskGroupDelById(id int) error {
|
||||
_, err := orm.NewOrm().QueryTable(TableName("task_group")).Filter("id", id).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
func TaskGroupGetList(page, pageSize int) ([]*TaskGroup, int64) {
|
||||
offset := (page - 1) * pageSize
|
||||
list := make([]*TaskGroup, 0)
|
||||
query := orm.NewOrm().QueryTable(TableName("task_group"))
|
||||
total, _ := query.Count()
|
||||
query.OrderBy("-id").Limit(pageSize, offset).All(&list)
|
||||
|
||||
return list, total
|
||||
}
|
||||
76
V1/models/task_log.go
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* @Author: haodaquan
|
||||
* @Date: 2017-06-21 12:23:22
|
||||
* @Last Modified by: haodaquan
|
||||
* @Last Modified time: 2017-06-22 14:57:13
|
||||
*/
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/orm"
|
||||
)
|
||||
|
||||
type TaskLog struct {
|
||||
Id int
|
||||
TaskId int
|
||||
Output string
|
||||
Error string
|
||||
Status int
|
||||
ProcessTime int
|
||||
CreateTime int64
|
||||
}
|
||||
|
||||
func (t *TaskLog) TableName() string {
|
||||
return TableName("task_log")
|
||||
}
|
||||
|
||||
func TaskLogAdd(t *TaskLog) (int64, error) {
|
||||
return orm.NewOrm().Insert(t)
|
||||
}
|
||||
|
||||
func TaskLogGetList(page, pageSize int, filters ...interface{}) ([]*TaskLog, int64) {
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
logs := make([]*TaskLog, 0)
|
||||
|
||||
query := orm.NewOrm().QueryTable(TableName("task_log"))
|
||||
if len(filters) > 0 {
|
||||
l := len(filters)
|
||||
for k := 0; k < l; k += 2 {
|
||||
query = query.Filter(filters[k].(string), filters[k+1])
|
||||
}
|
||||
}
|
||||
|
||||
total, _ := query.Count()
|
||||
query.OrderBy("-id").Limit(pageSize, offset).All(&logs)
|
||||
|
||||
return logs, total
|
||||
}
|
||||
|
||||
func TaskLogGetById(id int) (*TaskLog, error) {
|
||||
obj := &TaskLog{
|
||||
Id: id,
|
||||
}
|
||||
|
||||
err := orm.NewOrm().Read(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func TaskLogDelById(id int) error {
|
||||
_, err := orm.NewOrm().QueryTable(TableName("task_log")).Filter("id", id).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
func TaskLogDelByTaskId(taskId int) (int64, error) {
|
||||
return orm.NewOrm().QueryTable(TableName("task_log")).Filter("task_id", taskId).Delete()
|
||||
}
|
||||
|
||||
// func GetTodaySuccessNum() (num, error) {
|
||||
// o := orm.NewOrm()
|
||||
// var r RawSeter
|
||||
// r = o.Raw("SELECT COUNT(*) AS num WHERE create_time>=? AND status<0", "")
|
||||
// }
|
||||
18
V1/routers/router.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/controllers"
|
||||
)
|
||||
|
||||
func init() {
|
||||
beego.Router("/", &controllers.MainController{}, "*:Index")
|
||||
beego.Router("/login", &controllers.MainController{}, "*:Login")
|
||||
beego.Router("/logout", &controllers.MainController{}, "*:Logout")
|
||||
beego.Router("/profile", &controllers.MainController{}, "*:Profile")
|
||||
beego.Router("/gettime", &controllers.MainController{}, "*:GetTime")
|
||||
beego.Router("/help", &controllers.HelpController{}, "*:Index")
|
||||
beego.AutoRouter(&controllers.TaskController{})
|
||||
beego.AutoRouter(&controllers.GroupController{})
|
||||
beego.AutoRouter(&controllers.ServerController{})
|
||||
}
|
||||
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 243 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
67
V1/views/group/add.html
Normal file
@@ -0,0 +1,67 @@
|
||||
<!-- 新增任务 -->
|
||||
<div class="container-fluid">
|
||||
<div class="info-center">
|
||||
<!--title-->
|
||||
<div class="info-center">
|
||||
<div class="page-header">
|
||||
<div class="pull-left">
|
||||
<h4>{{.pageTitle}}</h4>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<!-- <button type="button" class="btn btn-mystyle btn-sm refresh">刷新</button>
|
||||
<button type="button" class="btn btn-mystyle btn-sm reback">返回</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!--content-list-->
|
||||
<div class="content-list">
|
||||
<form action="{{urlfor "GroupController.Add"}}" method="post" class="form-horizontal">
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="group_name">分类名称</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="" name="group_name" value="" required />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="description">分类说明</label>
|
||||
<div class="col-sm-5" >
|
||||
<textarea name="description" class="form-control" id="description" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div class="modal-footer" style="text-align:center">
|
||||
<button type="submit" class="btn btn-primary submit_attr_button">保存</button>
|
||||
<button type="button" class="btn btn-default reback">返回</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
$("form").submit(function () {
|
||||
// $(".alert").hide();
|
||||
$("button[type='submit']").attr('disabled', true);
|
||||
$.post('{{urlfor "GroupController.Add"}}', $(this).serialize(), function (out) {
|
||||
if (out.status == 0) {
|
||||
window.location.href = '{{urlfor "GroupController.List"}}';
|
||||
} else {
|
||||
alert_message(out.msg,"alert-danger","alert-success");
|
||||
$("button[type='submit']").attr('disabled', false);
|
||||
}
|
||||
}, "json");
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
67
V1/views/group/edit.html
Normal file
@@ -0,0 +1,67 @@
|
||||
<!-- 新增任务 -->
|
||||
<div class="container-fluid">
|
||||
<div class="info-center">
|
||||
<!--title-->
|
||||
<div class="info-center">
|
||||
<div class="page-header">
|
||||
<div class="pull-left">
|
||||
<h4>{{.pageTitle}}</h4>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<!-- <button type="button" class="btn btn-mystyle btn-sm refresh">刷新</button>
|
||||
<button type="button" class="btn btn-mystyle btn-sm reback">返回</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!--content-list-->
|
||||
<div class="content-list">
|
||||
<form action="{{urlfor "GroupController.Edit"}}" method="post" class="form-horizontal">
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="group_name">分类名称</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="" name="group_name" value="{{.group.GroupName}}" required />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="description">分类说明</label>
|
||||
<div class="col-sm-5" >
|
||||
<textarea name="description" class="form-control" id="description" rows="3">{{.group.Description}}</textarea>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<input type="hidden" name="id" value="{{.group.Id}}" />
|
||||
<div class="modal-footer" style="text-align:center">
|
||||
<button type="submit" class="btn btn-primary submit_attr_button">保存</button>
|
||||
<button type="button" class="btn btn-default reback">返回</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
$("form").submit(function () {
|
||||
$("button[type='submit']").attr('disabled', true);
|
||||
$.post('{{urlfor "GroupController.Edit"}}', $(this).serialize(), function (out) {
|
||||
if (out.status == 0) {
|
||||
window.location.href = '{{urlfor "GroupController.List"}}';
|
||||
} else {
|
||||
alert_message(out.msg,"alert-danger","alert-success");
|
||||
$("button[type='submit']").attr('disabled', false);
|
||||
}
|
||||
}, "json");
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
113
V1/views/group/list.html
Normal file
@@ -0,0 +1,113 @@
|
||||
<!-- 分组列表 -->
|
||||
<div class="container-fluid">
|
||||
<div class="info-center">
|
||||
<!--title-->
|
||||
<div class="info-center">
|
||||
<div class="page-header">
|
||||
<div class="pull-left">
|
||||
<h4>{{.pageTitle}}</h4>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<!-- <button type="button" class="btn btn-mystyle btn-sm refresh">刷新</button>
|
||||
<button type="button" class="btn btn-mystyle btn-sm reback">返回</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<!--content-list-->
|
||||
<div class="content-list">
|
||||
<div class="search-box row">
|
||||
<div class="col-md-4">
|
||||
<div class="btn-group pull-left" role="group" aria-label="...">
|
||||
<a href='{{urlfor "GroupController.Add"}}' class="btn btn-primary"><span class="glyphicon glyphicon-plus"></span> 新增分类</a>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="glyphicon glyphicon-edit"></span> 批量操作
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="javascript:;" onclick="javascript:batch('delete');"><span class="glyphicon glyphicon-remove-sign" aria-hidden="true"></span> 删除</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class=" btn-large pull-right" >
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
<div class="table-margin">
|
||||
<form id="form-list" method="post" action="">
|
||||
<table class="table table-bordered table-header">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="all_check" /></td>
|
||||
<td>ID</td>
|
||||
<td width="20%">分类名称</td>
|
||||
<td>描述</td>
|
||||
<td width="25%">操作</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $k,$v := .list}}
|
||||
<tr>
|
||||
<td class="chk"><input type="checkbox" name="ids" value="{{$v.Id}}" /></td>
|
||||
<td class="center">{{$v.Id}}</td>
|
||||
<td>{{$v.GroupName}}</td>
|
||||
<td>{{$v.Description}}</td>
|
||||
|
||||
<td>
|
||||
|
||||
<a class="btn btn-info btn-xs" href="{{urlfor "GroupController.Edit"}}?id={{$v.Id}}">
|
||||
<span class="glyphicon glyphicon-file" aria-hidden="true"></span> 编辑
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="7">
|
||||
<div class="pull-right">
|
||||
{{str2html .pageBar}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function batch(action) {
|
||||
if ($("input[name='ids']:checked").size() < 1) {
|
||||
alert_message("请选择要操作的任务","alert-danger","alert-success");
|
||||
} else {
|
||||
if(action=='delete'){
|
||||
if(!confirm("确定要删除所选吗?")) return;
|
||||
}
|
||||
var url = "{{urlfor "GroupController.Batch"}}";
|
||||
$.post(url + "?action=" + action, $("#form-list").serialize(), function(out) {
|
||||
if (out.status != 0) {
|
||||
alert_message(out.msg,"alert-danger","alert-success");
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
}, "json");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//点击行换色
|
||||
$('tbody tr').click(function(){
|
||||
$(this).addClass("warning").siblings().removeClass("warning");
|
||||
});
|
||||
</script>
|
||||
349
V1/views/public/layout.html
Normal file
@@ -0,0 +1,349 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1, user-scalable=no">
|
||||
<title>定时任务管理后台</title>
|
||||
<link href="/static/bootstrap/css/bootstrap.min.css" title="" rel="stylesheet" />
|
||||
|
||||
<link title="" href="/static/css/style.css" rel="stylesheet" type="text/css" />
|
||||
<link title="blue" href="/static/css/dermadefault.css" rel="stylesheet" type="text/css"/>
|
||||
<link title="green" href="/static/css/dermagreen.css" rel="stylesheet" type="text/css" disabled="disabled"/>
|
||||
<link title="orange" href="/static/css/dermaorange.css" rel="stylesheet" type="text/css" disabled="disabled"/>
|
||||
|
||||
<link href="/static/css/templatecss.css" rel="stylesheet" title="" type="text/css" />
|
||||
<script src="/static/js/jquery-1.11.1.min.js" type="text/javascript"></script>
|
||||
<script src="/static/js/jquery.cookie.js" type="text/javascript"></script>
|
||||
<script src="/static/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="nav navbar-default navbar-mystyle navbar-fixed-top">
|
||||
<div class="navbar-header">
|
||||
<button class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand mystyle-brand">
|
||||
<span class="glyphicon glyphicon-time"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="li-border">
|
||||
<a class="mystyle-color" href="#" style="font-size: 20px">定时任务管理后台 <span style="font-size: 12px">V1.2</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav navbar-nav pull-right">
|
||||
<li class="li-border">
|
||||
|
||||
<a class="mystyle-color" href="#" id="server-time" style="font-size: 10px"></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav navbar-nav pull-right">
|
||||
<li class="li-border">
|
||||
<a href="#" class="mystyle-color">
|
||||
<span class="glyphicon glyphicon-bell"></span>
|
||||
<span class="topbar-num">0</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="dropdown li-border">
|
||||
<a href="#" class="dropdown-toggle mystyle-color" data-toggle="dropdown">
|
||||
帮助文档<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/help">帮助文档</a></li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown li-border">
|
||||
<a href="#" class="dropdown-toggle mystyle-color" data-toggle="dropdown">
|
||||
{{.loginUserName}}<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/logout">退出</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle mystyle-color" data-toggle="dropdown">
|
||||
换肤<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu changecolor">
|
||||
<li id="blue"><a href="#">蓝色</a></li>
|
||||
<li class="divider"></li>
|
||||
<li id="green"><a href="#">绿色</a></li>
|
||||
<li class="divider"></li>
|
||||
<li id="orange"><a href="#">橙色</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
<div class="down-main">
|
||||
<div class="left-main left-full">
|
||||
<div class="sidebar-fold">
|
||||
<span class="glyphicon glyphicon-menu-hamburger"></span>
|
||||
</div>
|
||||
|
||||
<div class="subNavBox">
|
||||
<div class="sBox">
|
||||
<ul class="navContent" >
|
||||
<li {{if eq .curRoute "main.index"}}class="active"{{end}} id="home_page">
|
||||
<a href="/">
|
||||
<span class="sublist-icon glyphicon glyphicon-home"></span><span class="sub-title">系统首页</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="subNavBox">
|
||||
<div class="sBox">
|
||||
<div class="subNav sublist-down">
|
||||
<span class="title-icon glyphicon glyphicon-chevron-down"></span>
|
||||
<span class="sublist-title">任务管理</span>
|
||||
</div>
|
||||
<ul class="navContent" style="display:block">
|
||||
|
||||
<li {{if eq .menuTag "task"}}class="active"{{end}}>
|
||||
<div class="showtitle" style="width:100px;">
|
||||
任务列表
|
||||
</div>
|
||||
<a href="/task/list">
|
||||
<span class="sublist-icon glyphicon glyphicon-th-list"></span>
|
||||
<span class="sub-title">任务列表</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li {{if eq .menuTag "group"}}class="active"{{end}}>
|
||||
<div class="showtitle" style="width:100px;">
|
||||
任务分类
|
||||
</div>
|
||||
<a href="/group/list">
|
||||
<span class="sublist-icon glyphicon glyphicon-bookmark"></span>
|
||||
<span class="sub-title">任务分类</span>
|
||||
</a>
|
||||
</li>
|
||||
<li {{if eq .curRoute "help.index"}}class="active"{{end}}>
|
||||
<div class="showtitle" style="width:100px;">
|
||||
使用帮助
|
||||
</div>
|
||||
<a href="/help">
|
||||
<span class="sublist-icon glyphicon glyphicon-question-sign"></span>
|
||||
<span class="sub-title">使用帮助</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="subNavBox">
|
||||
<div class="sBox">
|
||||
<div class="subNav sublist-down">
|
||||
<span class="title-icon glyphicon glyphicon-chevron-down"></span>
|
||||
<span class="sublist-title">资源管理</span>
|
||||
</div>
|
||||
<ul class="navContent" >
|
||||
|
||||
<li {{if eq .menuTag "server"}}class="active"{{end}}>
|
||||
<div class="showtitle" style="width:100px;">
|
||||
服务器
|
||||
</div>
|
||||
<a href="/server/list">
|
||||
<span class="sublist-icon glyphicon glyphicon-hdd"></span>
|
||||
<span class="sub-title">服务器</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="subNavBox">
|
||||
<div class="sBox">
|
||||
<div class="subNav sublist-down">
|
||||
<span class="title-icon glyphicon glyphicon-chevron-down"></span>
|
||||
<span class="sublist-title">账户管理</span>
|
||||
</div>
|
||||
<ul class="navContent" >
|
||||
|
||||
<li {{if eq .curRoute "main.profile"}}class="active"{{end}}>
|
||||
<div class="showtitle" style="width:100px;">
|
||||
资料修改
|
||||
</div>
|
||||
<a href="/profile">
|
||||
<span class="sublist-icon glyphicon glyphicon-user"></span>
|
||||
<span class="sub-title">资料修改</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-product view-product right-full">
|
||||
<!--message start-->
|
||||
<div class="message">
|
||||
<div class="alert alert-danger box" role="alert" style="padding: 0px; line-height: 40px">
|
||||
<div class="col-sm-1 pull-right">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-11 pull-left">
|
||||
<strong id="message">保存成功</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--message end-->
|
||||
{{.LayoutContent}}
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
function alert_message(message,addClass,removeClass)
|
||||
{
|
||||
$('.box').addClass(addClass);
|
||||
$('.box').removeClass(removeClass);
|
||||
$("#message").html(message);
|
||||
$('.message').fadeIn(1000);
|
||||
$('.message').fadeOut(5000);
|
||||
}
|
||||
$(function(){
|
||||
/*换肤*/
|
||||
$(".dropdown .changecolor li").click(function(){
|
||||
var style = $(this).attr("id");
|
||||
$("link[title!='']").attr("disabled","disabled");
|
||||
$("link[title='"+style+"']").removeAttr("disabled");
|
||||
|
||||
$.cookie('mystyle', style, { expires: 7 }); // 存储一个带7天期限的 cookie
|
||||
})
|
||||
var cookie_style = $.cookie("mystyle");
|
||||
if(cookie_style!=null){
|
||||
$("link[title!='']").attr("disabled","disabled");
|
||||
$("link[title='"+cookie_style+"']").removeAttr("disabled");
|
||||
}
|
||||
/*左侧导航栏显示隐藏功能*/
|
||||
$(".subNav").click(function(){
|
||||
/*显示*/
|
||||
if($(this).find("span:first-child").attr('class')=="title-icon glyphicon glyphicon-chevron-down")
|
||||
{
|
||||
$(this).find("span:first-child").removeClass("glyphicon-chevron-down");
|
||||
$(this).find("span:first-child").addClass("glyphicon-chevron-up");
|
||||
$(this).removeClass("sublist-down");
|
||||
$(this).addClass("sublist-up");
|
||||
}
|
||||
/*隐藏*/
|
||||
else
|
||||
{
|
||||
$(this).find("span:first-child").removeClass("glyphicon-chevron-up");
|
||||
$(this).find("span:first-child").addClass("glyphicon-chevron-down");
|
||||
$(this).removeClass("sublist-up");
|
||||
$(this).addClass("sublist-down");
|
||||
}
|
||||
// 修改数字控制速度, slideUp(500)控制卷起速度
|
||||
$(this).next(".navContent").slideToggle(300).siblings(".navContent").slideUp(300);
|
||||
})
|
||||
/*左侧导航栏缩进功能*/
|
||||
$(".left-main .sidebar-fold").click(function(){
|
||||
if($(this).parent().attr('class')=="left-main left-full")
|
||||
{
|
||||
$(this).parent().removeClass("left-full");
|
||||
$(this).parent().addClass("left-off");
|
||||
|
||||
$(this).parent().parent().find(".right-product").removeClass("right-full");
|
||||
$(this).parent().parent().find(".right-product").addClass("right-off");
|
||||
}
|
||||
else
|
||||
{
|
||||
$(this).parent().removeClass("left-off");
|
||||
$(this).parent().addClass("left-full");
|
||||
|
||||
$(this).parent().parent().find(".right-product").removeClass("right-off");
|
||||
$(this).parent().parent().find(".right-product").addClass("right-full");
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
/*左侧鼠标移入提示功能*/
|
||||
$(".sBox ul li").mouseenter(function(){
|
||||
if($(this).find("span:last-child").css("display")=="none")
|
||||
{$(this).find("div").show();}
|
||||
}).mouseleave(function(){$(this).find("div").hide();})
|
||||
});
|
||||
|
||||
/*刷新*/
|
||||
$(".refresh").on("click",function() {
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
/*返回上一页*/
|
||||
$(".reback").on("click",function() {
|
||||
window.history.go(-1);
|
||||
});
|
||||
|
||||
/*全选*/
|
||||
$("input[name=all_check]").click(function(){
|
||||
var all_status = this.checked;
|
||||
//全选
|
||||
if(all_status == true){
|
||||
$(".chk").find("input[type=checkbox]").each(function(){
|
||||
|
||||
if($(this).val()>0){
|
||||
$(this).prop("checked",true);
|
||||
}
|
||||
});
|
||||
}
|
||||
if(all_status == false){
|
||||
//反选
|
||||
$(".chk").find("input[type=checkbox]:checked").each(function(){
|
||||
if($(this).val()>0){
|
||||
$(this).prop("checked",false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//服务器时间
|
||||
Date.prototype.Format = function (fmt) {
|
||||
var o = {
|
||||
"M+": this.getMonth() + 1,
|
||||
"d+": this.getDate(),
|
||||
"h+": this.getHours(),
|
||||
"m+": this.getMinutes(),
|
||||
"s+": this.getSeconds(),
|
||||
"q+": Math.floor((this.getMonth() + 3) / 3),
|
||||
"S": this.getMilliseconds()
|
||||
};
|
||||
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
|
||||
for (var k in o)
|
||||
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
|
||||
return fmt;
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$('.subnavbar').find ('li').each (function (i) {
|
||||
var mod = i % 3;
|
||||
if (mod === 2) {
|
||||
$(this).addClass ('subnavbar-open-right');
|
||||
}
|
||||
});
|
||||
initTime = new Date().getTime();
|
||||
$.getJSON("/gettime", function(out) {
|
||||
setTime(initTime, out.time);
|
||||
});
|
||||
});
|
||||
|
||||
function setTime(initTime,serverTime) {
|
||||
ellapsedTime = new Date().getTime()-initTime;
|
||||
$('#server-time').html('当前服务器时间: <strong>'+new Date(serverTime+ellapsedTime).Format("yyyy-MM-dd hh:mm:ss")+'</strong>');
|
||||
setTimeout('setTime('+initTime+','+serverTime+');',500);
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
158
V1/views/server/add.html
Normal file
@@ -0,0 +1,158 @@
|
||||
<!-- 新增服务器 -->
|
||||
<div class="container-fluid">
|
||||
<div class="info-center">
|
||||
<!--title-->
|
||||
<div class="info-center">
|
||||
<div class="page-header">
|
||||
<div class="pull-left">
|
||||
<h4>{{.pageTitle}}</h4>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<!-- <button type="button" class="btn btn-mystyle btn-sm refresh">刷新</button>
|
||||
<button type="button" class="btn btn-mystyle btn-sm reback">返回</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!--content-list-->
|
||||
<div class="content-list">
|
||||
<form action="{{urlfor "ServerController.Add"}}" method="post" class="form-horizontal">
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="server_name">服务器名</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="" name="server_name" value="" required />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="server_account">登录账户</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="root" name="server_account" value="" required />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="server_ip">服务器IP</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="" name="server_ip" value="" required />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="port">服务器ssh端口</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="22" name="port" value="" required />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="type">验证类型</label>
|
||||
<div class="col-sm-6" >
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="type" value="0" > 密码
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="type" value="1" checked > 密钥
|
||||
</label>
|
||||
|
||||
</div>
|
||||
<div class="col-sm-3" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group hide" style="margin-top: 15px" id="password">
|
||||
<label class="col-sm-3 control-label" for="password">服务器密码</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="" name="password" value="" />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group " style="margin-top: 15px" id="private_key_src">
|
||||
<label class="col-sm-3 control-label" for="private_key_src">私钥地址</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="/Users/haodaquan/.ssh/pp_rsa" name="private_key_src" value="" />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group " style="margin-top: 15px" id="public_key_src">
|
||||
<label class="col-sm-3 control-label" for="public_key_src">公钥地址</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="/Users/haodaquan/.ssh/pp_rsa.pub" name="public_key_src" value="" />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
<i style="font-size: 12px">公钥和私钥地址请在本地服务器生成,命令:ssh-keygen -t rsa -f pp_rsa</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="detail">说明</label>
|
||||
<div class="col-sm-5" >
|
||||
<textarea name="detail" class="form-control" id="detail" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<br />
|
||||
<div class="modal-footer" style="text-align:center">
|
||||
<button type="submit" class="btn btn-primary submit_attr_button">保存</button>
|
||||
<button type="button" class="btn btn-default reback">返回</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
$("form").submit(function () {
|
||||
// $(".alert").hide();
|
||||
$("button[type='submit']").attr('disabled', true);
|
||||
$.post('{{urlfor "ServerController.Add"}}', $(this).serialize(), function (out) {
|
||||
if (out.status == 0) {
|
||||
window.location.href = '{{urlfor "ServerController.List"}}';
|
||||
} else {
|
||||
alert_message(out.msg,"alert-danger","alert-success");
|
||||
$("button[type='submit']").attr('disabled', false);
|
||||
}
|
||||
}, "json");
|
||||
return false;
|
||||
});
|
||||
|
||||
$("input[name='type']").click(function () {
|
||||
if ($(this).val() > 0) {
|
||||
$("#password").addClass('hide');
|
||||
$("#public_key_src").removeClass('hide');
|
||||
$("#private_key_src").removeClass('hide');
|
||||
} else {
|
||||
$("#password").removeClass('hide');
|
||||
$("#public_key_src").addClass('hide');
|
||||
$("#private_key_src").addClass('hide');
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
160
V1/views/server/edit.html
Normal file
@@ -0,0 +1,160 @@
|
||||
<!-- 新增服务器 -->
|
||||
<div class="container-fluid">
|
||||
<div class="info-center">
|
||||
<!--title-->
|
||||
<div class="info-center">
|
||||
<div class="page-header">
|
||||
<div class="pull-left">
|
||||
<h4>{{.pageTitle}}</h4>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<!-- <button type="button" class="btn btn-mystyle btn-sm refresh">刷新</button>
|
||||
<button type="button" class="btn btn-mystyle btn-sm reback">返回</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!--content-list-->
|
||||
<div class="content-list">
|
||||
<form action="{{urlfor "ServerController.Edit"}}" method="post" class="form-horizontal">
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="server_name">服务器名</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="" name="server_name" value="{{.server.ServerName}}" required />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="server_name">登录账户</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="root" name="server_account" value="{{.server.ServerAccount}}" required />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="server_ip">服务器IP</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="" name="server_ip" value="{{.server.ServerIp}}" required />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="port">服务器ssh端口</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="" name="port" value="{{.server.Port}}" required />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="type">验证类型</label>
|
||||
<div class="col-sm-6" >
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="type" value="0" {{if eq .server.Type 0}}checked{{end}}> 密码
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="type" value="1" {{if eq .server.Type 1}}checked{{end}}> 密钥
|
||||
</label>
|
||||
|
||||
</div>
|
||||
<div class="col-sm-3" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group {{if eq .server.Type 1}}hide{{end}}" style="margin-top: 15px" id="password">
|
||||
<label class="col-sm-3 control-label" for="password">服务器密码</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="/Users/haodaquan/.ssh/pp_rsa" name="password" value="{{.server.Password}}" />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group {{if eq .server.Type 0}}hide{{end}}" style="margin-top: 15px" id="private_key_src">
|
||||
<label class="col-sm-3 control-label" for="private_key_src">私钥地址</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="/Users/haodaquan/.ssh/pp_rsa.pub" name="private_key_src" value="{{.server.PrivateKeySrc}}" />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group {{if eq .server.Type 0}}hide{{end}}" style="margin-top: 15px" id="public_key_src">
|
||||
<label class="col-sm-3 control-label" for="public_key_src">公钥地址</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="" name="public_key_src" value="{{.server.PublicKeySrc}}" />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="detail">说明</label>
|
||||
<div class="col-sm-5" >
|
||||
<textarea name="detail" class="form-control" id="detail" rows="3">{{.server.Detail}}</textarea>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<br />
|
||||
<div class="modal-footer" style="text-align:center">
|
||||
<input type="hidden" name="id" value="{{.server.Id}}" />
|
||||
<button type="submit" class="btn btn-primary submit_attr_button">保存</button>
|
||||
<button type="button" class="btn btn-default reback">返回</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
$("form").submit(function () {
|
||||
// $(".alert").hide();
|
||||
$("button[type='submit']").attr('disabled', true);
|
||||
$.post('{{urlfor "ServerController.Edit"}}', $(this).serialize(), function (out) {
|
||||
if (out.status == 0) {
|
||||
window.location.href = '{{urlfor "ServerController.List"}}';
|
||||
} else {
|
||||
alert_message(out.msg,"alert-danger","alert-success");
|
||||
$("button[type='submit']").attr('disabled', false);
|
||||
}
|
||||
}, "json");
|
||||
return false;
|
||||
});
|
||||
|
||||
$("input[name='type']").click(function () {
|
||||
if ($(this).val() > 0) {
|
||||
$("#password").addClass('hide');
|
||||
$("#public_key_src").removeClass('hide');
|
||||
$("#private_key_src").removeClass('hide');
|
||||
} else {
|
||||
$("#password").removeClass('hide');
|
||||
$("#public_key_src").addClass('hide');
|
||||
$("#private_key_src").addClass('hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
121
V1/views/server/list.html
Normal file
@@ -0,0 +1,121 @@
|
||||
<!-- 分组列表 -->
|
||||
<div class="container-fluid">
|
||||
<div class="info-center">
|
||||
<!--title-->
|
||||
<div class="info-center">
|
||||
<div class="page-header">
|
||||
<div class="pull-left">
|
||||
<h4>{{.pageTitle}}</h4>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<!-- <button type="button" class="btn btn-mystyle btn-sm refresh">刷新</button>
|
||||
<button type="button" class="btn btn-mystyle btn-sm reback">返回</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<!--content-list-->
|
||||
<div class="content-list">
|
||||
<div class="search-box row">
|
||||
<div class="col-md-4">
|
||||
<div class="btn-group pull-left" role="group" aria-label="...">
|
||||
<a href='{{urlfor "ServerController.Add"}}' class="btn btn-primary"><span class="glyphicon glyphicon-plus"></span> 新增服务器</a>
|
||||
<div class="btn-group" role="server">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="glyphicon glyphicon-edit"></span> 批量操作
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="javascript:;" onclick="javascript:batch('delete');"><span class="glyphicon glyphicon-remove-sign" aria-hidden="true"></span> 删除</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class=" btn-large pull-right" >
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
<div class="table-margin">
|
||||
<form id="form-list" method="post" action="">
|
||||
<table class="table table-bordered table-header">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="all_check" /></td>
|
||||
<td>ID</td>
|
||||
<td width="20%">服务器名称</td>
|
||||
<td width="">IP地址</td>
|
||||
<td width="">端口号</td>
|
||||
<td width="">验证类型</td>
|
||||
<td width="20%">备注</td>
|
||||
<td width="">创建时间</td>
|
||||
<td width="10%">操作</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $k,$v := .list}}
|
||||
<tr>
|
||||
<td class="chk"><input type="checkbox" name="ids" value="{{$v.id}}" /></td>
|
||||
<td class="center">{{$v.id}}</td>
|
||||
<td>{{$v.server_name}}</td>
|
||||
<td>{{$v.server_ip}}</td>
|
||||
<td>{{$v.port}}</td>
|
||||
<td>{{$v.type}}</td>
|
||||
<td>{{$v.detail}}</td>
|
||||
<td>{{$v.create_time}}</td>
|
||||
|
||||
<td>
|
||||
|
||||
<a class="btn btn-info btn-xs" href="{{urlfor "ServerController.Edit"}}?id={{$v.id}}">
|
||||
<span class="glyphicon glyphicon-file" aria-hidden="true"></span> 编辑
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="9">
|
||||
<div class="pull-right">
|
||||
{{str2html .pageBar}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function batch(action) {
|
||||
if ($("input[name='ids']:checked").size() < 1) {
|
||||
alert_message("请选择要操作的任务","alert-danger","alert-success");
|
||||
} else {
|
||||
if(action=='delete'){
|
||||
if(!confirm("确定要删除所选吗?")) return;
|
||||
}
|
||||
var url = "{{urlfor "ServerController.Batch"}}";
|
||||
$.post(url + "?action=" + action, $("#form-list").serialize(), function(out) {
|
||||
if (out.status != 0) {
|
||||
alert_message(out.msg,"alert-danger","alert-success");
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
}, "json");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//点击行换色
|
||||
$('tbody tr').click(function(){
|
||||
$(this).addClass("warning").siblings().removeClass("warning");
|
||||
});
|
||||
</script>
|
||||
143
V1/views/task/add.html
Normal file
@@ -0,0 +1,143 @@
|
||||
<!-- 新增任务 -->
|
||||
<div class="container-fluid">
|
||||
<div class="info-center">
|
||||
<!--title-->
|
||||
<div class="info-center">
|
||||
<div class="page-header">
|
||||
<div class="pull-left">
|
||||
<h4>{{.pageTitle}}</h4>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<!-- <button type="button" class="btn btn-mystyle btn-sm refresh">刷新</button>
|
||||
<button type="button" class="btn btn-mystyle btn-sm reback">返回</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!--content-list-->
|
||||
<div class="content-list">
|
||||
<form action="{{urlfor "TaskController.Add"}}" method="post" class="form-horizontal">
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="task_name">任务名称</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="不需要带分组名称" name="task_name" value="" required />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
<i style="color:red">*</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="description">任务说明</label>
|
||||
<div class="col-sm-5" >
|
||||
<textarea name="description" class="form-control" id="description" rows="3" placeholder="注明执行周期"></textarea>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="group_id">任务分组</label>
|
||||
<div class="col-sm-3" >
|
||||
<select name="group_id" class="form-control">
|
||||
<option value="0">未分组</option>
|
||||
{{range $k, $v := .groups}}
|
||||
<option value="{{$v.Id}}">{{$v.GroupName}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="server_id">服务器</label>
|
||||
<div class="col-sm-3" >
|
||||
<select name="server_id" class="form-control">
|
||||
<option value="0">本地服务器</option>
|
||||
{{range $ks, $vs := .servers}}
|
||||
<option value="{{$vs.Id}}">{{$vs.ServerName}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="concurrent">是否单例</label>
|
||||
<div class="col-sm-3" >
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="concurrent" value="0" checked> 是
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="concurrent" value="1"> 否
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
<i style="font-size: 12px">设为“是”的话,如果该任务在上一个时间点还没执行完,则略过不执行</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="cron_spec">时间表达式</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="" name="cron_spec" value="" />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
<a href="{{urlfor "HelpController.Index"}}" target="_blank"><i style="font-size: 12px">支持秒级定时,详见《参见使用帮助》</i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="command">命令脚本</label>
|
||||
<div class="col-sm-5" >
|
||||
<textarea name="command" class="form-control" id="command" rows="3" placeholder="支持bash命令和shell文件"></textarea>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-top:5px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="timeout">超时设置</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="秒,默认一天" name="timeout" value="" />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<div class="modal-footer" style="text-align:center">
|
||||
<button type="submit" class="btn btn-primary submit_attr_button">保存</button>
|
||||
<button type="button" class="btn btn-default reback">返回</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
$("form").submit(function () {
|
||||
$("button[type='submit']").attr('disabled', true);
|
||||
$.post('{{urlfor "TaskController.Add"}}', $(this).serialize(), function (out) {
|
||||
if (out.status == 0) {
|
||||
window.location.href = '{{urlfor "TaskController.List"}}';
|
||||
} else {
|
||||
alert_message(out.msg,"alert-danger","alert-success");
|
||||
$("button[type='submit']").attr('disabled', false);
|
||||
}
|
||||
}, "json");
|
||||
return false;
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
144
V1/views/task/copy.html
Normal file
@@ -0,0 +1,144 @@
|
||||
<!-- 新增任务 -->
|
||||
<div class="container-fluid">
|
||||
<div class="info-center">
|
||||
<!--title-->
|
||||
<div class="info-center">
|
||||
<div class="page-header">
|
||||
<div class="pull-left">
|
||||
<h4>{{.pageTitle}}</h4>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<!-- <button type="button" class="btn btn-mystyle btn-sm refresh">刷新</button>
|
||||
<button type="button" class="btn btn-mystyle btn-sm reback">返回</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!--content-list-->
|
||||
<div class="content-list">
|
||||
<form action="{{urlfor "TaskController.Add"}}" method="post" class="form-horizontal">
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="task_name">任务名称</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="" name="task_name" value="{{.task.TaskName}}" />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="task_name">任务说明</label>
|
||||
<div class="col-sm-5" >
|
||||
<textarea name="description" class="form-control" id="description" rows="3">{{.task.Description}}</textarea>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="task_name">任务分组</label>
|
||||
<div class="col-sm-3" >
|
||||
<select name="group_id" class="form-control">
|
||||
<option value="0">未分组</option>
|
||||
{{range $k, $v := .groups}}
|
||||
<option value="{{$v.Id}}" {{if eq $v.Id $.task.GroupId}}selected{{end}}>{{$v.GroupName}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="server_id">服务器</label>
|
||||
<div class="col-sm-3" >
|
||||
<select name="server_id" class="form-control">
|
||||
<option value="0">本地服务器</option>
|
||||
{{range $ks, $vs := .servers}}
|
||||
<option value="{{$vs.Id}}" {{if eq $vs.Id $.task.ServerId}}selected{{end}}>{{$vs.ServerName}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="concurrent">是否单例</label>
|
||||
<div class="col-sm-3" >
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="concurrent" value="0" {{if eq .task.Concurrent 0}}checked{{end}}> 是
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="concurrent" value="1" {{if eq .task.Concurrent 1}}checked{{end}}> 否
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
<i>设为“是”的话,如果该任务在上一个时间点还没执行完,则略过不执行</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="cron_spec">时间表达式</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="" name="cron_spec" value="{{.task.CronSpec}}" />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
<a href="{{urlfor "HelpController.Index"}}" target="_blank">参见使用帮助</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="task_name">命令脚本</label>
|
||||
<div class="col-sm-5" >
|
||||
<textarea name="command" class="form-control" id="command" rows="3">{{.task.Command}}</textarea>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="cron_spec">超时设置</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="秒" name="timeout" value="{{.task.Timeout}}" />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
单位秒,不设置的话,默认超时时间为1天
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<div class="modal-footer" style="text-align:center">
|
||||
<button type="submit" class="btn btn-primary submit_attr_button">保存</button>
|
||||
<button type="button" class="btn btn-default reback">返回</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
$("form").submit(function () {
|
||||
$("button[type='submit']").attr('disabled', true);
|
||||
$.post('{{urlfor "TaskController.Add"}}', $(this).serialize(), function (out) {
|
||||
if (out.status == 0) {
|
||||
window.location.href = '{{urlfor "TaskController.List"}}';
|
||||
} else {
|
||||
alert_message(out.msg,"alert-danger","alert-success");
|
||||
$("button[type='submit']").attr('disabled', false);
|
||||
}
|
||||
}, "json");
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
145
V1/views/task/edit.html
Normal file
@@ -0,0 +1,145 @@
|
||||
<!-- 新增任务 -->
|
||||
<div class="container-fluid">
|
||||
<div class="info-center">
|
||||
<!--title-->
|
||||
<div class="info-center">
|
||||
<div class="page-header">
|
||||
<div class="pull-left">
|
||||
<h4>{{.pageTitle}}</h4>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<!-- <button type="button" class="btn btn-mystyle btn-sm refresh">刷新</button>
|
||||
<button type="button" class="btn btn-mystyle btn-sm reback">返回</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!--content-list-->
|
||||
<div class="content-list">
|
||||
<form action="{{urlfor "TaskController.Add"}}" method="post" class="form-horizontal">
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="task_name">任务名称</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="" name="task_name" value="{{.task.TaskName}}" />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="task_name">任务说明</label>
|
||||
<div class="col-sm-5" >
|
||||
<textarea name="description" class="form-control" id="description" rows="3">{{.task.Description}}</textarea>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="task_name">任务分组</label>
|
||||
<div class="col-sm-3" >
|
||||
<select name="group_id" class="form-control">
|
||||
<option value="0">未分组</option>
|
||||
{{range $k, $v := .groups}}
|
||||
<option value="{{$v.Id}}" {{if eq $v.Id $.task.GroupId}}selected{{end}}>{{$v.GroupName}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="server_id">服务器</label>
|
||||
<div class="col-sm-3" >
|
||||
<select name="server_id" class="form-control">
|
||||
<option value="0">本地服务器</option>
|
||||
{{range $ks, $vs := .servers}}
|
||||
<option value="{{$vs.Id}}" {{if eq $vs.Id $.task.ServerId}}selected{{end}}>{{$vs.ServerName}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="concurrent">是否单例</label>
|
||||
<div class="col-sm-3" >
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="concurrent" value="0" {{if eq .task.Concurrent 0}}checked{{end}}> 是
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="concurrent" value="1" {{if eq .task.Concurrent 1}}checked{{end}}> 否
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
<i>设为“是”的话,如果该任务在上一个时间点还没执行完,则略过不执行</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="cron_spec">时间表达式</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="" name="cron_spec" value="{{.task.CronSpec}}" />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
<a href="{{urlfor "HelpController.Index"}}" target="_blank">参见使用帮助</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="task_name">命令脚本</label>
|
||||
<div class="col-sm-5" >
|
||||
<textarea name="command" class="form-control" id="command" rows="3">{{.task.Command}}</textarea>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-top:5px;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group" style="margin-top: 15px">
|
||||
<label class="col-sm-3 control-label" for="cron_spec">超时设置</label>
|
||||
<div class="col-sm-3" >
|
||||
<input type="text" class="form-control input-sm" placeholder="秒" name="timeout" value="{{.task.Timeout}}" />
|
||||
</div>
|
||||
<div class="col-sm-6" style="padding-top:5px;">
|
||||
单位秒,不设置的话,默认超时时间为1天
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<input type="hidden" name="id" value="{{.task.Id}}" />
|
||||
<div class="modal-footer" style="text-align:center">
|
||||
<button type="submit" class="btn btn-primary submit_attr_button">保存</button>
|
||||
<button type="button" class="btn btn-default reback">返回</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
$("form").submit(function () {
|
||||
$("button[type='submit']").attr('disabled', true);
|
||||
$.post('{{urlfor "TaskController.Edit"}}', $(this).serialize(), function (out) {
|
||||
if (out.status == 0) {
|
||||
window.location.href = '{{urlfor "TaskController.List"}}';
|
||||
} else {
|
||||
alert_message(out.msg,"alert-danger","alert-success");
|
||||
$("button[type='submit']").attr('disabled', false);
|
||||
}
|
||||
}, "json");
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
170
V1/views/task/list.html
Normal file
@@ -0,0 +1,170 @@
|
||||
<!-- 任务列表 -->
|
||||
<div class="container-fluid">
|
||||
<div class="info-center">
|
||||
<!--title-->
|
||||
<div class="info-center">
|
||||
<div class="page-header">
|
||||
<div class="pull-left">
|
||||
<h4>{{.pageTitle}}</h4>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<!-- <button type="button" class="btn btn-mystyle btn-sm refresh">刷新</button>
|
||||
<button type="button" class="btn btn-mystyle btn-sm reback">返回</button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<!--content-list-->
|
||||
<div class="content-list">
|
||||
<div class="search-box row">
|
||||
<div class="col-md-4">
|
||||
<div class="btn-group pull-left" role="group" aria-label="...">
|
||||
<a href='{{urlfor "TaskController.Add"}}' class="btn btn-primary"><span class="glyphicon glyphicon-plus"></span> 新增任务</a>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="glyphicon glyphicon-edit"></span> 批量操作
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="javascript:;" onclick="javascript:batch('active');"><span class="glyphicon glyphicon-expand" aria-hidden="true"></span> 激活</a></li>
|
||||
<li><a href="javascript:;" onclick="javascript:batch('pause');"><span class="glyphicon glyphicon-pause" aria-hidden="true"></span> 暂停</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="javascript:;" onclick="javascript:batch('delete');"><span class="glyphicon glyphicon-remove-sign" aria-hidden="true"></span> 删除</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class=" btn-large pull-right" >
|
||||
<form method="post" name="s" action="{{urlfor "TaskController.List"}}">
|
||||
<select name="groupid" class="btn-large form-control">
|
||||
<option value="99">全部分组</option>
|
||||
{{range $k, $v := .groups}}
|
||||
<option value="{{$v.Id}}" {{if eq $v.Id $.groupid}}selected{{end}} >{{$v.GroupName}}</option>
|
||||
{{end}}
|
||||
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
<div class="table-margin">
|
||||
<form id="form-list" method="post" action="">
|
||||
<table class="table table-bordered table-header">
|
||||
<thead>
|
||||
<tr>
|
||||
<td><input type="checkbox" name="all_check" /></td>
|
||||
<td>ID</td>
|
||||
<td width="20%">任务名称</td>
|
||||
<td>服务器</td>
|
||||
<td>任务说明</td>
|
||||
<td>上次执行时间</td>
|
||||
<td>下次执行时间</td>
|
||||
<td width="25%">操作</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $k,$v := .list}}
|
||||
<tr {{if eq $v.is_odd 0 }} style="background: #FFFFFF " {{else}} style="background: #f8f8f8" {{end}}>
|
||||
<td class="chk"><input type="checkbox" name="ids" value="{{$v.id}}" /></td>
|
||||
<td> {{$v.id}} </td>
|
||||
<td>
|
||||
{{if eq $v.running 0}}
|
||||
<span class="glyphicon glyphicon-minus-sign brand-danger" aria-hidden="true"></span>
|
||||
{{else}}
|
||||
<span class="glyphicon glyphicon-ok-sign brand-success " aria-hidden="true"></span>
|
||||
{{end}}
|
||||
{{$v.group_name}}-{{$v.name}}
|
||||
</td>
|
||||
<td> {{$v.server_name}} </td>
|
||||
|
||||
<td> {{$v.description}} </td>
|
||||
<td> {{$v.prev_time}} </td>
|
||||
<td> {{$v.next_time}} </td>
|
||||
<td>
|
||||
{{if eq $v.status 0}}
|
||||
<a class="btn btn-danger btn-xs" href="{{urlfor "TaskController.Start"}}?id={{$v.id}}">
|
||||
<span class="glyphicon glyphicon-expand" aria-hidden="true"></span> 激活
|
||||
</a>
|
||||
<a class="btn btn-info btn-xs" href="{{urlfor "TaskController.Edit"}}?id={{$v.id}}">
|
||||
<span class="glyphicon glyphicon-edit" aria-hidden="true"></span> 编辑
|
||||
</a>
|
||||
{{else}}
|
||||
<a class="btn btn-success btn-xs" href="{{urlfor "TaskController.Pause"}}?id={{$v.id}}">
|
||||
<span class="glyphicon glyphicon-pause" aria-hidden="true"></span> 暂停
|
||||
</a>
|
||||
<a class="btn btn-default btn-xs" href="javascript:void(0)" onclick="alert('激活状态无法编辑任务,请先暂停任务');">
|
||||
<span class="glyphicon glyphicon-edit" aria-hidden="true"></span> 编辑
|
||||
</a>
|
||||
{{end}}
|
||||
<a class="btn btn-info btn-run btn-xs" href="{{urlfor "TaskController.Run"}}?id={{$v.id}}">
|
||||
<span class="glyphicon glyphicon-flash" aria-hidden="true"></span> 执行
|
||||
</a>
|
||||
<a class="btn btn-info btn-xs" href="{{urlfor "TaskController.Logs"}}?id={{$v.id}}">
|
||||
<span class="glyphicon glyphicon-file" aria-hidden="true"></span> 日志
|
||||
</a>
|
||||
|
||||
<a class="btn btn-info btn-xs" href="{{urlfor "TaskController.Copy"}}?id={{$v.id}}">
|
||||
<span class="glyphicon glyphicon-copy" aria-hidden="true"></span> 复制
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="8">
|
||||
<div class="pull-right">
|
||||
{{str2html .pageBar}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
$('[data-toggle="tooltip"]').tooltip()
|
||||
$("select[name='groupid']").change(function () {
|
||||
$("form[name='s']").submit();
|
||||
});
|
||||
$(".btn-run").click(function () {
|
||||
return confirm("该功能建议只用来做任务测试,确定要立即执行该任务吗?");
|
||||
});
|
||||
});
|
||||
|
||||
function batch(action) {
|
||||
|
||||
if ($("input[name=ids]:checked").size() < 1) {
|
||||
alert_message("请选择要操作的任务","alert-danger","alert-success");
|
||||
} else {
|
||||
|
||||
if(action=='delete'){
|
||||
if(!confirm("确定要删除所选吗?")) return;
|
||||
}
|
||||
|
||||
var url = "{{urlfor "TaskController.Batch"}}";
|
||||
$.post(url + "?action=" + action, $("#form-list").serialize(), function(out) {
|
||||
if (out.status != 0) {
|
||||
alert_message(out.msg,"alert-danger","alert-success");
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
}, "json");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
//点击行换色
|
||||
$('tbody tr').click(function(){
|
||||
$(this).addClass("warning").siblings().removeClass("warning");
|
||||
});
|
||||
</script>
|
||||
@@ -1,18 +1,21 @@
|
||||
appname = PPGo_Job
|
||||
appname = PPGo_Job2
|
||||
httpport = 8080
|
||||
runmode = dev
|
||||
|
||||
version= V2.0
|
||||
|
||||
# 允许同时运行的任务数
|
||||
jobs.pool = 1000
|
||||
|
||||
# 站点名称
|
||||
site.name = 定时任务管理器
|
||||
|
||||
|
||||
# 数据库配置
|
||||
db.host = 127.0.0.1
|
||||
db.user = root
|
||||
db.password = "123456"
|
||||
db.port = 3306
|
||||
db.name = ppgo_job
|
||||
db.name = ppgo_job2
|
||||
db.prefix = pp_
|
||||
db.timezone = Asia/Shanghai
|
||||
|
||||
215
controllers/admin.go
Normal file
@@ -0,0 +1,215 @@
|
||||
/**********************************************
|
||||
** @Des: 管理员
|
||||
** @Author: haodaquan
|
||||
** @Date: 2017-09-16 14:17:37
|
||||
** @Last Modified by: haodaquan
|
||||
** @Last Modified time: 2017-09-17 11:14:07
|
||||
***********************************************/
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
)
|
||||
|
||||
type AdminController struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
func (self *AdminController) List() {
|
||||
self.Data["pageTitle"] = "管理员管理"
|
||||
self.display()
|
||||
//self.TplName = "admin/list.html"
|
||||
}
|
||||
|
||||
func (self *AdminController) Add() {
|
||||
self.Data["pageTitle"] = "新增管理员"
|
||||
|
||||
// 角色
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "status", 1)
|
||||
result, _ := models.RoleGetList(1, 1000, filters...)
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["role_name"] = v.RoleName
|
||||
list[k] = row
|
||||
}
|
||||
|
||||
self.Data["role"] = list
|
||||
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *AdminController) Edit() {
|
||||
self.Data["pageTitle"] = "编辑管理员"
|
||||
|
||||
id, _ := self.GetInt("id", 0)
|
||||
Admin, _ := models.AdminGetById(id)
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = Admin.Id
|
||||
row["login_name"] = Admin.LoginName
|
||||
row["real_name"] = Admin.RealName
|
||||
row["phone"] = Admin.Phone
|
||||
row["email"] = Admin.Email
|
||||
row["role_ids"] = Admin.RoleIds
|
||||
self.Data["admin"] = row
|
||||
|
||||
role_ids := strings.Split(Admin.RoleIds, ",")
|
||||
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "status", 1)
|
||||
result, _ := models.RoleGetList(1, 1000, filters...)
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["checked"] = 0
|
||||
for i := 0; i < len(role_ids); i++ {
|
||||
role_id, _ := strconv.Atoi(role_ids[i])
|
||||
if role_id == v.Id {
|
||||
row["checked"] = 1
|
||||
}
|
||||
fmt.Println(role_ids[i])
|
||||
}
|
||||
row["id"] = v.Id
|
||||
row["role_name"] = v.RoleName
|
||||
list[k] = row
|
||||
}
|
||||
self.Data["role"] = list
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *AdminController) AjaxSave() {
|
||||
Admin_id, _ := self.GetInt("id")
|
||||
if Admin_id == 0 {
|
||||
Admin := new(models.Admin)
|
||||
Admin.LoginName = strings.TrimSpace(self.GetString("login_name"))
|
||||
Admin.RealName = strings.TrimSpace(self.GetString("real_name"))
|
||||
Admin.Phone = strings.TrimSpace(self.GetString("phone"))
|
||||
Admin.Email = strings.TrimSpace(self.GetString("email"))
|
||||
Admin.RoleIds = strings.TrimSpace(self.GetString("roleids"))
|
||||
Admin.UpdateTime = time.Now().Unix()
|
||||
Admin.UpdateId = self.userId
|
||||
Admin.Status = 1
|
||||
|
||||
// 检查登录名是否已经存在
|
||||
_, err := models.AdminGetByName(Admin.LoginName)
|
||||
|
||||
if err == nil {
|
||||
self.ajaxMsg("登录名已经存在", MSG_ERR)
|
||||
}
|
||||
//新增
|
||||
pwd, salt := libs.Password(4, "")
|
||||
Admin.Password = pwd
|
||||
Admin.Salt = salt
|
||||
Admin.CreateTime = time.Now().Unix()
|
||||
Admin.CreateId = self.userId
|
||||
if _, err := models.AdminAdd(Admin); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
Admin, _ := models.AdminGetById(Admin_id)
|
||||
//修改
|
||||
Admin.Id = Admin_id
|
||||
Admin.UpdateTime = time.Now().Unix()
|
||||
Admin.UpdateId = self.userId
|
||||
Admin.LoginName = strings.TrimSpace(self.GetString("login_name"))
|
||||
Admin.RealName = strings.TrimSpace(self.GetString("real_name"))
|
||||
Admin.Phone = strings.TrimSpace(self.GetString("phone"))
|
||||
Admin.Email = strings.TrimSpace(self.GetString("email"))
|
||||
Admin.RoleIds = strings.TrimSpace(self.GetString("roleids"))
|
||||
Admin.UpdateTime = time.Now().Unix()
|
||||
Admin.UpdateId = self.userId
|
||||
Admin.Status = 1
|
||||
|
||||
resetPwd, _ := self.GetInt("reset_pwd")
|
||||
if resetPwd == 1 {
|
||||
pwd, salt := libs.Password(4, "")
|
||||
Admin.Password = pwd
|
||||
Admin.Salt = salt
|
||||
}
|
||||
|
||||
//普通管理员不可修改超级管理员资料
|
||||
if self.userId != 1 && Admin.Id == 1 {
|
||||
self.ajaxMsg("不可修改超级管理员资料", MSG_ERR)
|
||||
}
|
||||
if err := Admin.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg(strconv.Itoa(resetPwd), MSG_OK)
|
||||
}
|
||||
|
||||
func (self *AdminController) AjaxDel() {
|
||||
|
||||
Admin_id, _ := self.GetInt("id")
|
||||
status := strings.TrimSpace(self.GetString("status"))
|
||||
if Admin_id == 1 {
|
||||
self.ajaxMsg("超级管理员不允许操作", MSG_ERR)
|
||||
}
|
||||
|
||||
Admin_status := 0
|
||||
if status == "enable" {
|
||||
Admin_status = 1
|
||||
}
|
||||
Admin, _ := models.AdminGetById(Admin_id)
|
||||
Admin.UpdateTime = time.Now().Unix()
|
||||
Admin.Status = Admin_status
|
||||
Admin.Id = Admin_id
|
||||
|
||||
if err := Admin.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("操作成功", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *AdminController) Table() {
|
||||
//列表
|
||||
page, err := self.GetInt("page")
|
||||
if err != nil {
|
||||
page = 1
|
||||
}
|
||||
limit, err := self.GetInt("limit")
|
||||
if err != nil {
|
||||
limit = 30
|
||||
}
|
||||
|
||||
realName := strings.TrimSpace(self.GetString("realName"))
|
||||
|
||||
StatusText := make(map[int]string)
|
||||
StatusText[0] = "<font color='red'>禁用</font>"
|
||||
StatusText[1] = "正常"
|
||||
|
||||
self.pageSize = limit
|
||||
//查询条件
|
||||
filters := make([]interface{}, 0)
|
||||
//
|
||||
if realName != "" {
|
||||
filters = append(filters, "real_name__icontains", realName)
|
||||
}
|
||||
result, count := models.AdminGetList(page, self.pageSize, filters...)
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["login_name"] = v.LoginName
|
||||
row["real_name"] = v.RealName
|
||||
row["phone"] = v.Phone
|
||||
row["email"] = v.Email
|
||||
row["role_ids"] = v.RoleIds
|
||||
row["create_time"] = beego.Date(time.Unix(v.CreateTime, 0), "Y-m-d H:i:s")
|
||||
row["update_time"] = beego.Date(time.Unix(v.UpdateTime, 0), "Y-m-d H:i:s")
|
||||
row["status"] = v.Status
|
||||
row["status_text"] = StatusText[v.Status]
|
||||
list[k] = row
|
||||
}
|
||||
self.ajaxList("成功", MSG_OK, count, list)
|
||||
}
|
||||
118
controllers/auth.go
Normal file
@@ -0,0 +1,118 @@
|
||||
/**********************************************
|
||||
** @Des: 权限因子
|
||||
** @Author: haodaquan
|
||||
** @Date: 2017-09-09 16:14:31
|
||||
** @Last Modified by: haodaquan
|
||||
** @Last Modified time: 2017-09-17 11:23:40
|
||||
***********************************************/
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
)
|
||||
|
||||
type AuthController struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
func (self *AuthController) Index() {
|
||||
|
||||
self.Data["pageTitle"] = "权限因子"
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *AuthController) List() {
|
||||
self.Data["zTree"] = true //引入ztreecss
|
||||
self.Data["pageTitle"] = "权限因子"
|
||||
self.display()
|
||||
}
|
||||
|
||||
//获取全部节点
|
||||
func (self *AuthController) GetNodes() {
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "status", 1)
|
||||
result, count := models.AuthGetList(1, 1000, filters...)
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["pId"] = v.Pid
|
||||
row["name"] = v.AuthName
|
||||
row["open"] = true
|
||||
list[k] = row
|
||||
}
|
||||
|
||||
self.ajaxList("成功", MSG_OK, count, list)
|
||||
}
|
||||
|
||||
//获取一个节点
|
||||
func (self *AuthController) GetNode() {
|
||||
id, _ := self.GetInt("id")
|
||||
result, _ := models.AuthGetById(id)
|
||||
// if err == nil {
|
||||
// self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
// }
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = result.Id
|
||||
row["pid"] = result.Pid
|
||||
row["auth_name"] = result.AuthName
|
||||
row["auth_url"] = result.AuthUrl
|
||||
row["sort"] = result.Sort
|
||||
row["is_show"] = result.IsShow
|
||||
row["icon"] = result.Icon
|
||||
|
||||
fmt.Println(row)
|
||||
|
||||
self.ajaxList("成功", MSG_OK, 0, row)
|
||||
}
|
||||
|
||||
//新增或修改
|
||||
func (self *AuthController) AjaxSave() {
|
||||
auth := new(models.Auth)
|
||||
auth.UserId = self.userId
|
||||
auth.Pid, _ = self.GetInt("pid")
|
||||
auth.AuthName = strings.TrimSpace(self.GetString("auth_name"))
|
||||
auth.AuthUrl = strings.TrimSpace(self.GetString("auth_url"))
|
||||
auth.Sort, _ = self.GetInt("sort")
|
||||
auth.IsShow, _ = self.GetInt("is_show")
|
||||
auth.Icon = strings.TrimSpace(self.GetString("icon"))
|
||||
auth.UpdateTime = time.Now().Unix()
|
||||
|
||||
auth.Status = 1
|
||||
|
||||
id, _ := self.GetInt("id")
|
||||
if id == 0 {
|
||||
//新增
|
||||
auth.CreateTime = time.Now().Unix()
|
||||
auth.CreateId = self.userId
|
||||
auth.UpdateId = self.userId
|
||||
if _, err := models.AuthAdd(auth); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
} else {
|
||||
auth.Id = id
|
||||
auth.UpdateId = self.userId
|
||||
if err := auth.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
}
|
||||
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
//删除
|
||||
func (self *AuthController) AjaxDel() {
|
||||
id, _ := self.GetInt("id")
|
||||
auth, _ := models.AuthGetById(id)
|
||||
auth.Id = id
|
||||
auth.Status = 0
|
||||
if err := auth.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
126
controllers/ban.go
Normal file
@@ -0,0 +1,126 @@
|
||||
/************************************************************
|
||||
** @Description: controllers
|
||||
** @Author: haodaquan
|
||||
** @Date: 2018-06-10 19:50
|
||||
** @Last Modified by: haodaquan
|
||||
** @Last Modified time: 2018-06-10 19:50
|
||||
*************************************************************/
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
)
|
||||
|
||||
type BanController struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
func (self *BanController) List() {
|
||||
self.Data["pageTitle"] = "禁用命令管理"
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *BanController) Add() {
|
||||
self.Data["pageTitle"] = "新增禁用命令"
|
||||
|
||||
// 角色
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "status", 1)
|
||||
result, _ := models.RoleGetList(1, 1000, filters...)
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["role_name"] = v.RoleName
|
||||
list[k] = row
|
||||
}
|
||||
|
||||
self.Data["role"] = list
|
||||
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *BanController) Edit() {
|
||||
self.Data["pageTitle"] = "编辑禁用命令"
|
||||
|
||||
id, _ := self.GetInt("id", 0)
|
||||
ban, _ := models.BanGetById(id)
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = ban.Id
|
||||
row["code"] = ban.Code
|
||||
self.Data["ban"] = row
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *BanController) AjaxSave() {
|
||||
id, _ := self.GetInt("id")
|
||||
if id == 0 {
|
||||
ban := new(models.Ban)
|
||||
ban.Code = strings.TrimSpace(self.GetString("code"))
|
||||
ban.CreateTime = time.Now().Unix()
|
||||
|
||||
if _, err := models.BanAdd(ban); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
ban, _ := models.BanGetById(id)
|
||||
//修改
|
||||
ban.Id = id
|
||||
ban.UpdateTime = time.Now().Unix()
|
||||
ban.Code = strings.TrimSpace(self.GetString("code"))
|
||||
|
||||
if err := ban.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *BanController) AjaxDel() {
|
||||
id, _ := self.GetInt("id")
|
||||
ban, _ := models.BanGetById(id)
|
||||
ban.UpdateTime = time.Now().Unix()
|
||||
ban.Status = 1
|
||||
|
||||
if err := ban.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("操作成功", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *BanController) Table() {
|
||||
//列表
|
||||
page, err := self.GetInt("page")
|
||||
if err != nil {
|
||||
page = 1
|
||||
}
|
||||
limit, err := self.GetInt("limit")
|
||||
if err != nil {
|
||||
limit = 30
|
||||
}
|
||||
|
||||
code := strings.TrimSpace(self.GetString("code"))
|
||||
|
||||
self.pageSize = limit
|
||||
//查询条件
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "status", 0)
|
||||
if code != "" {
|
||||
filters = append(filters, "code__icontains", code)
|
||||
}
|
||||
result, count := models.BanGetList(page, self.pageSize, filters...)
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["code"] = v.Code
|
||||
row["create_time"] = beego.Date(time.Unix(v.CreateTime, 0), "Y-m-d H:i:s")
|
||||
list[k] = row
|
||||
}
|
||||
self.ajaxList("成功", MSG_OK, count, list)
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
/*
|
||||
* @Author: haodaquan
|
||||
* @Date: 2017-06-19 22:27:09
|
||||
* @Last Modified by: haodaquan
|
||||
* @Last Modified time: 2017-06-22 11:15:33
|
||||
*/
|
||||
/**********************************************
|
||||
** @Des: base controller
|
||||
** @Author: haodaquan
|
||||
** @Date: 2017-09-07 16:54:40
|
||||
** @Last Modified by: haodaquan
|
||||
** @Last Modified time: 2017-09-18 10:28:01
|
||||
***********************************************/
|
||||
package controllers
|
||||
|
||||
import (
|
||||
@@ -23,106 +24,291 @@ type BaseController struct {
|
||||
beego.Controller
|
||||
controllerName string
|
||||
actionName string
|
||||
user *models.User
|
||||
user *models.Admin
|
||||
userId int
|
||||
userName string
|
||||
loginName string
|
||||
pageSize int
|
||||
allowUrl string
|
||||
serverGroups string
|
||||
taskGroups string
|
||||
}
|
||||
|
||||
func (this *BaseController) Prepare() {
|
||||
this.pageSize = 20
|
||||
controllerName, actionName := this.GetControllerAndAction()
|
||||
this.controllerName = strings.ToLower(controllerName[0 : len(controllerName)-10])
|
||||
this.actionName = strings.ToLower(actionName)
|
||||
this.auth()
|
||||
//前期准备
|
||||
func (self *BaseController) Prepare() {
|
||||
self.pageSize = 20
|
||||
controllerName, actionName := self.GetControllerAndAction()
|
||||
self.controllerName = strings.ToLower(controllerName[0 : len(controllerName)-10])
|
||||
self.actionName = strings.ToLower(actionName)
|
||||
self.Data["version"] = beego.AppConfig.String("version")
|
||||
self.Data["siteName"] = beego.AppConfig.String("site.name")
|
||||
self.Data["curRoute"] = self.controllerName + "." + self.actionName
|
||||
self.Data["curController"] = self.controllerName
|
||||
self.Data["curAction"] = self.actionName
|
||||
// noAuth := "ajaxsave/ajaxdel/table/loginin/loginout/getnodes/start"
|
||||
// isNoAuth := strings.Contains(noAuth, self.actionName)
|
||||
//fmt.Println(self.controllerName)
|
||||
//if (strings.Compare(self.controllerName, "apidoc")) != 0 {
|
||||
//
|
||||
//}
|
||||
|
||||
this.Data["version"] = beego.AppConfig.String("version")
|
||||
this.Data["siteName"] = beego.AppConfig.String("site.name")
|
||||
this.Data["curRoute"] = this.controllerName + "." + this.actionName
|
||||
this.Data["curController"] = this.controllerName
|
||||
this.Data["curAction"] = this.actionName
|
||||
this.Data["loginUserId"] = this.userId
|
||||
this.Data["loginUserName"] = this.userName
|
||||
this.Data["menuTag"] = this.controllerName
|
||||
self.Auth()
|
||||
self.Data["loginUserId"] = self.userId
|
||||
self.Data["loginUserName"] = self.userName
|
||||
}
|
||||
|
||||
//登录状态验证
|
||||
func (this *BaseController) auth() {
|
||||
arr := strings.Split(this.Ctx.GetCookie("auth"), "|")
|
||||
//登录权限验证
|
||||
func (self *BaseController) Auth() {
|
||||
arr := strings.Split(self.Ctx.GetCookie("auth"), "|")
|
||||
self.userId = 0
|
||||
if len(arr) == 2 {
|
||||
idstr, password := arr[0], arr[1]
|
||||
userId, _ := strconv.Atoi(idstr)
|
||||
if userId > 0 {
|
||||
user, err := models.UserGetById(userId)
|
||||
if err == nil && password == libs.Md5([]byte(this.getClientIp()+"|"+user.Password+user.Salt)) {
|
||||
this.userId = user.Id
|
||||
this.userName = user.UserName
|
||||
this.user = user
|
||||
user, err := models.AdminGetById(userId)
|
||||
|
||||
if err == nil && password == libs.Md5([]byte(self.getClientIp()+"|"+user.Password+user.Salt)) {
|
||||
self.userId = user.Id
|
||||
self.loginName = user.LoginName
|
||||
self.userName = user.RealName
|
||||
self.user = user
|
||||
self.AdminAuth()
|
||||
self.dataAuth(user)
|
||||
}
|
||||
|
||||
isHasAuth := strings.Contains(self.allowUrl, self.controllerName+"/"+self.actionName)
|
||||
noAuth := "ajaxsave/table/loginin/loginout/getnodes/start"
|
||||
isNoAuth := strings.Contains(noAuth, self.actionName)
|
||||
|
||||
if isHasAuth == false && isNoAuth == false {
|
||||
if strings.Contains(self.actionName, "ajax") {
|
||||
self.ajaxMsg("没有权限", MSG_ERR)
|
||||
return
|
||||
}
|
||||
|
||||
flash := beego.NewFlash()
|
||||
flash.Error("没有权限")
|
||||
flash.Store(&self.Controller)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if this.userId == 0 && (this.controllerName != "main" ||
|
||||
(this.controllerName == "main" && this.actionName != "logout" && this.actionName != "login")) {
|
||||
this.redirect(beego.URLFor("MainController.Login"))
|
||||
if self.userId == 0 && (self.controllerName != "login" && self.actionName != "loginin") {
|
||||
self.redirect(beego.URLFor("LoginController.Login"))
|
||||
}
|
||||
}
|
||||
|
||||
//渲染模版
|
||||
func (this *BaseController) display(tpl ...string) {
|
||||
var tplname string
|
||||
if len(tpl) > 0 {
|
||||
tplname = tpl[0] + ".html"
|
||||
} else {
|
||||
tplname = this.controllerName + "/" + this.actionName + ".html"
|
||||
func (self *BaseController) dataAuth(user *models.Admin) {
|
||||
if user.RoleIds == "0" || user.Id == 1 {
|
||||
return
|
||||
}
|
||||
this.Layout = "public/layout.html"
|
||||
this.TplName = tplname
|
||||
|
||||
Filters := make([]interface{}, 0)
|
||||
Filters = append(Filters, "status", 1)
|
||||
|
||||
RoleIdsArr := strings.Split(user.RoleIds, ",")
|
||||
|
||||
RoleIds := make([]int, 0)
|
||||
for _, v := range RoleIdsArr {
|
||||
id, _ := strconv.Atoi(v)
|
||||
RoleIds = append(RoleIds, id)
|
||||
}
|
||||
|
||||
Filters = append(Filters, "id__in", RoleIds)
|
||||
|
||||
Result, _ := models.RoleGetList(1, 1000, Filters...)
|
||||
serverGroups := ""
|
||||
taskGroups := ""
|
||||
for _, v := range Result {
|
||||
serverGroups += v.ServerGroupIds + ","
|
||||
taskGroups += v.TaskGroupIds + ","
|
||||
}
|
||||
|
||||
self.serverGroups = strings.Trim(serverGroups, ",")
|
||||
self.taskGroups = strings.Trim(taskGroups, ",")
|
||||
}
|
||||
|
||||
// 重定向
|
||||
func (this *BaseController) redirect(url string) {
|
||||
this.Redirect(url, 302)
|
||||
this.StopRun()
|
||||
func (self *BaseController) AdminAuth() {
|
||||
// 左侧导航栏
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "status", 1)
|
||||
if self.userId != 1 {
|
||||
//普通管理员
|
||||
adminAuthIds, _ := models.RoleAuthGetByIds(self.user.RoleIds)
|
||||
adminAuthIdArr := strings.Split(adminAuthIds, ",")
|
||||
filters = append(filters, "id__in", adminAuthIdArr)
|
||||
}
|
||||
result, _ := models.AuthGetList(1, 1000, filters...)
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
list2 := make([]map[string]interface{}, len(result))
|
||||
allow_url := ""
|
||||
i, j := 0, 0
|
||||
for _, v := range result {
|
||||
if v.AuthUrl != " " || v.AuthUrl != "/" {
|
||||
allow_url += v.AuthUrl
|
||||
}
|
||||
row := make(map[string]interface{})
|
||||
if v.Pid == 1 && v.IsShow == 1 {
|
||||
row["Id"] = int(v.Id)
|
||||
row["Sort"] = v.Sort
|
||||
row["AuthName"] = v.AuthName
|
||||
row["AuthUrl"] = v.AuthUrl
|
||||
row["Icon"] = v.Icon
|
||||
row["Pid"] = int(v.Pid)
|
||||
list[i] = row
|
||||
i++
|
||||
}
|
||||
if v.Pid != 1 && v.IsShow == 1 {
|
||||
row["Id"] = int(v.Id)
|
||||
row["Sort"] = v.Sort
|
||||
row["AuthName"] = v.AuthName
|
||||
row["AuthUrl"] = v.AuthUrl
|
||||
row["Icon"] = v.Icon
|
||||
row["Pid"] = int(v.Pid)
|
||||
list2[j] = row
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
self.Data["SideMenu1"] = list[:i] //一级菜单
|
||||
self.Data["SideMenu2"] = list2[:j] //二级菜单
|
||||
|
||||
self.allowUrl = allow_url + "/home/index"
|
||||
}
|
||||
|
||||
// 是否POST提交
|
||||
func (this *BaseController) isPost() bool {
|
||||
return this.Ctx.Request.Method == "POST"
|
||||
}
|
||||
|
||||
// 显示错误信息
|
||||
func (this *BaseController) showMsg(args ...string) {
|
||||
this.Data["message"] = args[0]
|
||||
redirect := this.Ctx.Request.Referer()
|
||||
if len(args) > 1 {
|
||||
redirect = args[1]
|
||||
}
|
||||
|
||||
this.Data["redirect"] = redirect
|
||||
this.Data["pageTitle"] = "系统提示"
|
||||
this.display("error/message")
|
||||
this.Render()
|
||||
this.StopRun()
|
||||
}
|
||||
|
||||
// 输出json
|
||||
func (this *BaseController) jsonResult(out interface{}) {
|
||||
this.Data["json"] = out
|
||||
this.ServeJSON()
|
||||
this.StopRun()
|
||||
}
|
||||
|
||||
func (this *BaseController) ajaxMsg(msg interface{}, msgno int) {
|
||||
out := make(map[string]interface{})
|
||||
out["status"] = msgno
|
||||
out["msg"] = msg
|
||||
|
||||
this.jsonResult(out)
|
||||
func (self *BaseController) isPost() bool {
|
||||
return self.Ctx.Request.Method == "POST"
|
||||
}
|
||||
|
||||
//获取用户IP地址
|
||||
func (this *BaseController) getClientIp() string {
|
||||
s := strings.Split(this.Ctx.Request.RemoteAddr, ":")
|
||||
func (self *BaseController) getClientIp() string {
|
||||
s := strings.Split(self.Ctx.Request.RemoteAddr, ":")
|
||||
return s[0]
|
||||
}
|
||||
|
||||
// 重定向
|
||||
func (self *BaseController) redirect(url string) {
|
||||
self.Redirect(url, 302)
|
||||
self.StopRun()
|
||||
}
|
||||
|
||||
//加载模板
|
||||
func (self *BaseController) display(tpl ...string) {
|
||||
var tplname string
|
||||
if len(tpl) > 0 {
|
||||
tplname = strings.Join([]string{tpl[0], "html"}, ".")
|
||||
} else {
|
||||
tplname = self.controllerName + "/" + self.actionName + ".html"
|
||||
}
|
||||
self.Layout = "public/layout.html"
|
||||
self.TplName = tplname
|
||||
}
|
||||
|
||||
//ajax返回
|
||||
func (self *BaseController) ajaxMsg(msg interface{}, msgno int) {
|
||||
out := make(map[string]interface{})
|
||||
out["status"] = msgno
|
||||
out["message"] = msg
|
||||
self.Data["json"] = out
|
||||
self.ServeJSON()
|
||||
self.StopRun()
|
||||
}
|
||||
|
||||
//ajax返回 列表
|
||||
func (self *BaseController) ajaxList(msg interface{}, msgno int, count int64, data interface{}) {
|
||||
out := make(map[string]interface{})
|
||||
out["code"] = msgno
|
||||
out["msg"] = msg
|
||||
out["count"] = count
|
||||
out["data"] = data
|
||||
self.Data["json"] = out
|
||||
self.ServeJSON()
|
||||
self.StopRun()
|
||||
}
|
||||
|
||||
//资源分组信息
|
||||
func serverGroupLists(authStr string, adminId int) (sgl map[int]string) {
|
||||
Filters := make([]interface{}, 0)
|
||||
Filters = append(Filters, "status", 1)
|
||||
if authStr != "0" && adminId != 1 {
|
||||
serverGroupIdsArr := strings.Split(authStr, ",")
|
||||
serverGroupIds := make([]int, 0)
|
||||
for _, v := range serverGroupIdsArr {
|
||||
id, _ := strconv.Atoi(v)
|
||||
serverGroupIds = append(serverGroupIds, id)
|
||||
}
|
||||
Filters = append(Filters, "id__in", serverGroupIds)
|
||||
}
|
||||
|
||||
groupResult, n := models.ServerGroupGetList(1, 1000, Filters...)
|
||||
sgl = make(map[int]string, n)
|
||||
for _, gv := range groupResult {
|
||||
sgl[gv.Id] = gv.GroupName
|
||||
}
|
||||
//sgl[0] = "默认分组"
|
||||
return sgl
|
||||
}
|
||||
|
||||
func taskGroupLists(authStr string, adminId int) (gl map[int]string) {
|
||||
groupFilters := make([]interface{}, 0)
|
||||
groupFilters = append(groupFilters, "status", 1)
|
||||
if authStr != "0" && adminId != 1 {
|
||||
taskGroupIdsArr := strings.Split(authStr, ",")
|
||||
taskGroupIds := make([]int, 0)
|
||||
for _, v := range taskGroupIdsArr {
|
||||
id, _ := strconv.Atoi(v)
|
||||
taskGroupIds = append(taskGroupIds, id)
|
||||
}
|
||||
groupFilters = append(groupFilters, "id__in", taskGroupIds)
|
||||
}
|
||||
groupResult, n := models.GroupGetList(1, 1000, groupFilters...)
|
||||
gl = make(map[int]string, n)
|
||||
for _, gv := range groupResult {
|
||||
gl[gv.Id] = gv.GroupName
|
||||
}
|
||||
return gl
|
||||
}
|
||||
|
||||
func serverListByGroupId(groupId int) []string {
|
||||
Filters := make([]interface{}, 0)
|
||||
Filters = append(Filters, "status", 1)
|
||||
Filters = append(Filters, "group_id", groupId)
|
||||
Result, _ := models.TaskServerGetList(1, 1000, Filters...)
|
||||
|
||||
servers := make([]string, 0)
|
||||
for _, v := range Result {
|
||||
servers = append(servers, strconv.Itoa(v.Id), v.ServerName)
|
||||
}
|
||||
|
||||
return servers
|
||||
}
|
||||
|
||||
type serverList struct {
|
||||
GroupId int
|
||||
GroupName string
|
||||
Servers map[int]string
|
||||
}
|
||||
|
||||
func serverLists(authStr string, adminId int) (sls []serverList) {
|
||||
serverGroup := serverGroupLists(authStr, adminId)
|
||||
Filters := make([]interface{}, 0)
|
||||
Filters = append(Filters, "status", 0)
|
||||
|
||||
Result, _ := models.TaskServerGetList(1, 1000, Filters...)
|
||||
for k, v := range serverGroup {
|
||||
sl := serverList{}
|
||||
sl.GroupId = k
|
||||
sl.GroupName = v
|
||||
servers := make(map[int]string)
|
||||
for _, sv := range Result {
|
||||
if sv.GroupId == k {
|
||||
servers[sv.Id] = sv.ServerName
|
||||
}
|
||||
}
|
||||
sl.Servers = servers
|
||||
sls = append(sls, sl)
|
||||
}
|
||||
return sls
|
||||
}
|
||||
|
||||
112
controllers/home.go
Normal file
@@ -0,0 +1,112 @@
|
||||
/**********************************************
|
||||
** @Des: This file ...
|
||||
** @Author: haodaquan
|
||||
** @Date: 2017-09-08 10:21:13
|
||||
** @Last Modified by: haodaquan
|
||||
** @Last Modified time: 2017-09-09 18:04:41
|
||||
***********************************************/
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/jobs"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HomeController struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
func (self *HomeController) Index() {
|
||||
self.Data["pageTitle"] = "系统首页"
|
||||
//self.display()
|
||||
self.TplName = "public/main.html"
|
||||
}
|
||||
|
||||
func (self *HomeController) Start() {
|
||||
groups_map := serverGroupLists(self.serverGroups, self.userId)
|
||||
//计算总任务数量
|
||||
_, count := models.TaskGetList(1, 200)
|
||||
// 即将执行的任务
|
||||
entries := jobs.GetEntries(30)
|
||||
jobList := make([]map[string]interface{}, len(entries))
|
||||
startJob := 0 //即将执行的任务
|
||||
for k, v := range entries {
|
||||
row := make(map[string]interface{})
|
||||
job := v.Job.(*jobs.Job)
|
||||
task, _ := models.TaskGetById(job.GetId())
|
||||
row["task_id"] = job.GetId()
|
||||
row["task_name"] = job.GetName()
|
||||
row["task_group"] = groups_map[task.GroupId]
|
||||
row["next_time"] = beego.Date(v.Next, "Y-m-d H:i:s")
|
||||
jobList[k] = row
|
||||
startJob++
|
||||
}
|
||||
|
||||
// 最近执行的日志
|
||||
logs, _ := models.TaskLogGetList(1, 20)
|
||||
recentLogs := make([]map[string]interface{}, len(logs))
|
||||
failJob := 0 //最近失败的数量
|
||||
okJob := 0 //最近成功的数量
|
||||
for k, v := range logs {
|
||||
task, err := models.TaskGetById(v.TaskId)
|
||||
taskName := ""
|
||||
if err == nil {
|
||||
taskName = task.TaskName
|
||||
}
|
||||
row := make(map[string]interface{})
|
||||
row["task_name"] = taskName
|
||||
row["id"] = v.Id
|
||||
row["start_time"] = beego.Date(time.Unix(v.CreateTime, 0), "Y-m-d H:i:s")
|
||||
row["process_time"] = float64(v.ProcessTime) / 1000
|
||||
row["ouput_size"] = libs.SizeFormat(float64(len(v.Output)))
|
||||
row["output"] = beego.Substr(v.Output, 0, 100)
|
||||
row["status"] = v.Status
|
||||
recentLogs[k] = row
|
||||
if v.Status != 0 {
|
||||
failJob++
|
||||
} else {
|
||||
okJob++
|
||||
}
|
||||
}
|
||||
|
||||
// 最近执行失败的日志
|
||||
logs, _ = models.TaskLogGetList(1, 20, "status__lt", 0)
|
||||
errLogs := make([]map[string]interface{}, len(logs))
|
||||
|
||||
for k, v := range logs {
|
||||
task, err := models.TaskGetById(v.TaskId)
|
||||
taskName := ""
|
||||
if err == nil {
|
||||
taskName = task.TaskName
|
||||
}
|
||||
|
||||
row := make(map[string]interface{})
|
||||
row["task_name"] = taskName
|
||||
row["id"] = v.Id
|
||||
row["start_time"] = beego.Date(time.Unix(v.CreateTime, 0), "Y-m-d H:i:s")
|
||||
row["process_time"] = float64(v.ProcessTime) / 1000
|
||||
row["ouput_size"] = libs.SizeFormat(float64(len(v.Output)))
|
||||
row["error"] = beego.Substr(v.Error, 0, 100)
|
||||
row["status"] = v.Status
|
||||
errLogs[k] = row
|
||||
|
||||
}
|
||||
|
||||
self.Data["startJob"] = startJob
|
||||
self.Data["okJob"] = okJob
|
||||
self.Data["failJob"] = failJob
|
||||
self.Data["totalJob"] = count
|
||||
|
||||
self.Data["recentLogs"] = recentLogs
|
||||
// this.Data["errLogs"] = errLogs
|
||||
self.Data["jobs"] = jobList
|
||||
self.Data["cpuNum"] = runtime.NumCPU()
|
||||
self.display()
|
||||
|
||||
self.Data["pageTitle"] = "系统概况"
|
||||
self.display()
|
||||
}
|
||||
73
controllers/login.go
Normal file
@@ -0,0 +1,73 @@
|
||||
/**********************************************
|
||||
** @Des: login
|
||||
** @Author: haodaquan
|
||||
** @Date: 2017-09-07 16:30:10
|
||||
** @Last Modified by: haodaquan
|
||||
** @Last Modified time: 2017-09-17 11:55:21
|
||||
***********************************************/
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
)
|
||||
|
||||
type LoginController struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
func (self *LoginController) Login() {
|
||||
//if self.userId > 0 {
|
||||
// self.redirect(beego.URLFor("HomeController.Index"))
|
||||
//}
|
||||
self.TplName = "login/login.html"
|
||||
}
|
||||
|
||||
//登录 TODO:XSRF过滤
|
||||
func (self *LoginController) LoginIn() {
|
||||
|
||||
//self.ajaxMsg("登录成功", MSG_OK)
|
||||
if self.userId > 0 {
|
||||
self.ajaxMsg("登录成功", MSG_OK)
|
||||
}
|
||||
|
||||
if self.isPost() {
|
||||
username := strings.TrimSpace(self.GetString("username"))
|
||||
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 {
|
||||
self.ajaxMsg("该帐号已禁用", MSG_ERR)
|
||||
} else {
|
||||
user.LastIp = self.getClientIp()
|
||||
user.LastLogin = time.Now().Unix()
|
||||
user.Update()
|
||||
authkey := libs.Md5([]byte(self.getClientIp() + "|" + user.Password + user.Salt))
|
||||
self.Ctx.SetCookie("auth", strconv.Itoa(user.Id)+"|"+authkey, 7*86400)
|
||||
|
||||
self.ajaxMsg("登录成功", MSG_OK)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.ajaxMsg("请求方式错误", MSG_ERR)
|
||||
}
|
||||
|
||||
//登出
|
||||
func (self *LoginController) LoginOut() {
|
||||
self.Ctx.SetCookie("auth", "")
|
||||
self.redirect(beego.URLFor("LoginController.Login"))
|
||||
}
|
||||
|
||||
func (self *LoginController) NoAuth() {
|
||||
self.Ctx.WriteString("没有权限")
|
||||
}
|
||||
184
controllers/role.go
Normal file
@@ -0,0 +1,184 @@
|
||||
/**********************************************
|
||||
** @Des: This file ...
|
||||
** @Author: haodaquan
|
||||
** @Date: 2017-09-14 14:23:32
|
||||
** @Last Modified by: haodaquan
|
||||
** @Last Modified time: 2017-09-17 11:31:13
|
||||
***********************************************/
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
)
|
||||
|
||||
type RoleController struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
func (self *RoleController) List() {
|
||||
self.Data["pageTitle"] = "角色管理"
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *RoleController) Add() {
|
||||
self.Data["zTree"] = true //引入ztreecss
|
||||
self.Data["taskGroup"] = taskGroupLists(self.taskGroups, self.userId)
|
||||
self.Data["serverGroup"] = serverLists(self.serverGroups, self.userId)
|
||||
self.Data["pageTitle"] = "新增角色"
|
||||
self.display()
|
||||
}
|
||||
func (self *RoleController) Edit() {
|
||||
self.Data["zTree"] = true //引入ztreecss
|
||||
self.Data["pageTitle"] = "编辑角色"
|
||||
|
||||
self.Data["taskGroup"] = taskGroupLists(self.taskGroups, self.userId)
|
||||
self.Data["serverGroup"] = serverLists(self.serverGroups, self.userId)
|
||||
|
||||
id, _ := self.GetInt("id", 0)
|
||||
role, _ := models.RoleGetById(id)
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = role.Id
|
||||
row["role_name"] = role.RoleName
|
||||
row["detail"] = role.Detail
|
||||
row["detail"] = role.Detail
|
||||
row["task_group_ids"] = role.TaskGroupIds
|
||||
row["server_group_ids"] = role.ServerGroupIds
|
||||
self.Data["role"] = row
|
||||
|
||||
//获取选择的树节点
|
||||
roleAuth, _ := models.RoleAuthGetById(id)
|
||||
authId := make([]int, 0)
|
||||
for _, v := range roleAuth {
|
||||
authId = append(authId, v.AuthId)
|
||||
}
|
||||
|
||||
taskGroupIdsArr := strings.Split(role.TaskGroupIds, ",")
|
||||
taskGroupIds := make([]int, 0)
|
||||
for _, v := range taskGroupIdsArr {
|
||||
id, _ := strconv.Atoi(v)
|
||||
taskGroupIds = append(taskGroupIds, id)
|
||||
}
|
||||
|
||||
serverGroupIdsArr := strings.Split(role.ServerGroupIds, ",")
|
||||
serverGroupIds := make([]int, 0)
|
||||
for _, v := range serverGroupIdsArr {
|
||||
id, _ := strconv.Atoi(v)
|
||||
serverGroupIds = append(serverGroupIds, id)
|
||||
}
|
||||
|
||||
self.Data["server_group_ids"] = serverGroupIds
|
||||
self.Data["task_group_ids"] = taskGroupIds
|
||||
|
||||
self.Data["auth"] = authId
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *RoleController) AjaxSave() {
|
||||
role := new(models.Role)
|
||||
role.RoleName = strings.TrimSpace(self.GetString("role_name"))
|
||||
role.Detail = strings.TrimSpace(self.GetString("detail"))
|
||||
role.ServerGroupIds = strings.TrimSpace(self.GetString("server_group_ids"))
|
||||
role.TaskGroupIds = strings.TrimSpace(self.GetString("task_group_ids"))
|
||||
role.CreateTime = time.Now().Unix()
|
||||
role.UpdateTime = time.Now().Unix()
|
||||
role.Status = 1
|
||||
|
||||
fmt.Println("=========", role)
|
||||
auths := strings.TrimSpace(self.GetString("nodes_data"))
|
||||
role_id, _ := self.GetInt("id")
|
||||
if role_id == 0 {
|
||||
//新增
|
||||
role.CreateTime = time.Now().Unix()
|
||||
role.UpdateTime = time.Now().Unix()
|
||||
role.CreateId = self.userId
|
||||
role.UpdateId = self.userId
|
||||
if id, err := models.RoleAdd(role); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
} else {
|
||||
ra := new(models.RoleAuth)
|
||||
authsSlice := strings.Split(auths, ",")
|
||||
for _, v := range authsSlice {
|
||||
aid, _ := strconv.Atoi(v)
|
||||
ra.AuthId = aid
|
||||
ra.RoleId = id
|
||||
models.RoleAuthAdd(ra)
|
||||
}
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
//修改
|
||||
role.Id = role_id
|
||||
role.UpdateTime = time.Now().Unix()
|
||||
role.UpdateId = self.userId
|
||||
if err := role.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
} else {
|
||||
// 删除该角色权限
|
||||
models.RoleAuthDelete(role_id)
|
||||
ra := new(models.RoleAuth)
|
||||
authsSlice := strings.Split(auths, ",")
|
||||
for _, v := range authsSlice {
|
||||
aid, _ := strconv.Atoi(v)
|
||||
ra.AuthId = aid
|
||||
ra.RoleId = int64(role_id)
|
||||
models.RoleAuthAdd(ra)
|
||||
}
|
||||
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *RoleController) AjaxDel() {
|
||||
|
||||
role_id, _ := self.GetInt("id")
|
||||
role, _ := models.RoleGetById(role_id)
|
||||
role.Status = 0
|
||||
role.Id = role_id
|
||||
role.UpdateTime = time.Now().Unix()
|
||||
|
||||
if err := role.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
// 删除该角色权限
|
||||
//models.RoleAuthDelete(role_id)
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *RoleController) Table() {
|
||||
//列表
|
||||
page, err := self.GetInt("page")
|
||||
if err != nil {
|
||||
page = 1
|
||||
}
|
||||
limit, err := self.GetInt("limit")
|
||||
if err != nil {
|
||||
limit = 30
|
||||
}
|
||||
|
||||
roleName := strings.TrimSpace(self.GetString("roleName"))
|
||||
self.pageSize = limit
|
||||
//查询条件
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "status", 1)
|
||||
if roleName != "" {
|
||||
filters = append(filters, "role_name__icontains", roleName)
|
||||
}
|
||||
result, count := models.RoleGetList(page, self.pageSize, filters...)
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["role_name"] = v.RoleName
|
||||
row["detail"] = v.Detail
|
||||
row["create_time"] = beego.Date(time.Unix(v.CreateTime, 0), "Y-m-d H:i:s")
|
||||
row["update_time"] = beego.Date(time.Unix(v.UpdateTime, 0), "Y-m-d H:i:s")
|
||||
list[k] = row
|
||||
}
|
||||
self.ajaxList("成功", MSG_OK, count, list)
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
/*
|
||||
* @Author: haodaquan
|
||||
* @Date: 2017-08-16 10:27:40
|
||||
* @Last Modified by: haodaquan
|
||||
* @Last Modified time: 2017-08-16 09:17:22
|
||||
*/
|
||||
|
||||
/************************************************************
|
||||
** @Description: controllers
|
||||
** @Author: haodaquan
|
||||
** @Date: 2018-06-09 16:11
|
||||
** @Last Modified by: haodaquan
|
||||
** @Last Modified time: 2018-06-09 16:11
|
||||
*************************************************************/
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
"fmt"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -20,118 +22,329 @@ type ServerController struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
func (this *ServerController) List() {
|
||||
page, _ := this.GetInt("page")
|
||||
if page < 1 {
|
||||
page = 1
|
||||
func (self *ServerController) List() {
|
||||
self.Data["pageTitle"] = "资源管理"
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *ServerController) Add() {
|
||||
self.Data["pageTitle"] = "新增服务器资源"
|
||||
self.Data["serverGroup"] = serverGroupLists(self.serverGroups, self.userId)
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *ServerController) GetServerByGroupId() {
|
||||
gid, _ := self.GetInt("gid", 0)
|
||||
if gid == 0 {
|
||||
self.ajaxMsg("groupId is not exist", MSG_ERR)
|
||||
}
|
||||
|
||||
result, count := models.TaskServerGetList(page, this.pageSize)
|
||||
//列表
|
||||
page, err := self.GetInt("page")
|
||||
if err != nil {
|
||||
page = 1
|
||||
}
|
||||
limit, err := self.GetInt("limit")
|
||||
if err != nil {
|
||||
limit = 30
|
||||
}
|
||||
//serverName := strings.TrimSpace(self.GetString("serverName"))
|
||||
StatusText := []string{
|
||||
"正常",
|
||||
"<font color='red'>禁用</font>",
|
||||
}
|
||||
|
||||
loginType := [2]string{
|
||||
"密码",
|
||||
"密钥",
|
||||
}
|
||||
|
||||
serverGroup := serverGroupLists(self.serverGroups, self.userId)
|
||||
|
||||
self.pageSize = limit
|
||||
//查询条件
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "status", 0)
|
||||
filters = append(filters, "group_id", gid)
|
||||
|
||||
result, count := models.TaskServerGetList(page, self.pageSize, filters...)
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
if(v.Type==0){
|
||||
row["type"] = "密码"
|
||||
}else {
|
||||
row["type"] = "密钥"
|
||||
}
|
||||
row["server_name"] = v.ServerName
|
||||
row["server_ip"] = v.ServerIp
|
||||
row["detail"] = v.Detail
|
||||
row["port"] = v.Port
|
||||
row["create_time"] = beego.Date(time.Unix(v.CreateTime, 0), "Y-m-d H:i:s")
|
||||
if serverGroup[v.GroupId] == "" {
|
||||
v.GroupId = 0
|
||||
}
|
||||
row["group_name"] = serverGroup[v.GroupId]
|
||||
row["type"] = loginType[v.Type]
|
||||
row["status"] = v.Status
|
||||
row["status_text"] = StatusText[v.Status]
|
||||
list[k] = row
|
||||
}
|
||||
this.Data["pageTitle"] = "服务器列表"
|
||||
this.Data["list"] = list
|
||||
this.Data["pageBar"] = libs.NewPager(page, int(count), this.pageSize, beego.URLFor("ServerController.List"), true).ToString()
|
||||
this.display()
|
||||
|
||||
self.ajaxList("成功", MSG_OK, count, list)
|
||||
}
|
||||
|
||||
func (this *ServerController) Add() {
|
||||
if this.isPost() {
|
||||
func (self *ServerController) Edit() {
|
||||
self.Data["pageTitle"] = "编辑服务器资源"
|
||||
|
||||
id, _ := self.GetInt("id", 0)
|
||||
server, _ := models.TaskServerGetById(id)
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = server.Id
|
||||
row["server_name"] = server.ServerName
|
||||
row["group_id"] = server.GroupId
|
||||
row["server_ip"] = server.ServerIp
|
||||
row["server_account"] = server.ServerAccount
|
||||
row["server_outer_ip"] = server.ServerOuterIp
|
||||
row["port"] = server.Port
|
||||
row["type"] = server.Type
|
||||
row["password"] = server.Password
|
||||
row["public_key_src"] = server.PublicKeySrc
|
||||
row["private_key_src"] = server.PrivateKeySrc
|
||||
row["detail"] = server.Detail
|
||||
self.Data["server"] = row
|
||||
self.Data["serverGroup"] = serverGroupLists(self.serverGroups, self.userId)
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *ServerController) AjaxTestServer() {
|
||||
|
||||
server := new(models.TaskServer)
|
||||
server.ServerName = strings.TrimSpace(self.GetString("server_name"))
|
||||
server.ServerAccount = strings.TrimSpace(self.GetString("server_account"))
|
||||
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"))
|
||||
server.Detail = strings.TrimSpace(self.GetString("detail"))
|
||||
server.Type, _ = self.GetInt("type")
|
||||
server.Port, _ = self.GetInt("port")
|
||||
server.GroupId, _ = self.GetInt("group_id")
|
||||
|
||||
var err error
|
||||
if server.Type == 0 {
|
||||
//密码登录
|
||||
err = RemoteCommandByPassword(server)
|
||||
}
|
||||
|
||||
if server.Type == 1 {
|
||||
//密钥登录
|
||||
err = RemoteCommandByKey(server)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("Success", MSG_OK)
|
||||
|
||||
}
|
||||
|
||||
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"] = "复制服务器资源"
|
||||
|
||||
id, _ := self.GetInt("id", 0)
|
||||
server, _ := models.TaskServerGetById(id)
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = server.Id
|
||||
row["server_name"] = server.ServerName
|
||||
row["group_id"] = server.GroupId
|
||||
row["server_ip"] = server.ServerIp
|
||||
row["server_account"] = server.ServerAccount
|
||||
row["server_outer_ip"] = server.ServerOuterIp
|
||||
row["port"] = server.Port
|
||||
row["type"] = server.Type
|
||||
row["password"] = server.Password
|
||||
row["public_key_src"] = server.PublicKeySrc
|
||||
row["private_key_src"] = server.PrivateKeySrc
|
||||
row["detail"] = server.Detail
|
||||
self.Data["server"] = row
|
||||
self.Data["serverGroup"] = serverGroupLists(self.serverGroups, self.userId)
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *ServerController) AjaxSave() {
|
||||
server_id, _ := self.GetInt("id")
|
||||
if server_id == 0 {
|
||||
server := new(models.TaskServer)
|
||||
server.ServerName = strings.TrimSpace(this.GetString("server_name"))
|
||||
server.ServerAccount = strings.TrimSpace(this.GetString("server_account"))
|
||||
server.ServerIp = strings.TrimSpace(this.GetString("server_ip"))
|
||||
server.Port,_= strconv.Atoi(this.GetString("port"))
|
||||
server.Type,_ = strconv.Atoi(this.GetString("type"))
|
||||
server.PrivateKeySrc = strings.TrimSpace(this.GetString("private_key_src"))
|
||||
server.PublicKeySrc = strings.TrimSpace(this.GetString("public_key_src"))
|
||||
server.Password = strings.TrimSpace(this.GetString("password"))
|
||||
server.Detail = strings.TrimSpace(this.GetString("detail"))
|
||||
server.ServerName = strings.TrimSpace(self.GetString("server_name"))
|
||||
server.ServerAccount = strings.TrimSpace(self.GetString("server_account"))
|
||||
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"))
|
||||
|
||||
server.Detail = strings.TrimSpace(self.GetString("detail"))
|
||||
server.Type, _ = self.GetInt("type")
|
||||
server.Port, _ = self.GetInt("port")
|
||||
server.GroupId, _ = self.GetInt("group_id")
|
||||
|
||||
server.CreateTime = time.Now().Unix()
|
||||
server.UpdateTime = time.Now().Unix()
|
||||
server.Status = 0
|
||||
_, err := models.TaskServerAdd(server)
|
||||
if err != nil {
|
||||
this.ajaxMsg(err.Error(), MSG_ERR)
|
||||
|
||||
if _, err := models.TaskServerAdd(server); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
this.ajaxMsg("", MSG_OK)
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
this.Data["pageTitle"] = "添加服务器"
|
||||
this.display()
|
||||
|
||||
server, _ := models.TaskServerGetById(server_id)
|
||||
//修改
|
||||
server.Id = server_id
|
||||
server.UpdateTime = time.Now().Unix()
|
||||
|
||||
server.ServerName = strings.TrimSpace(self.GetString("server_name"))
|
||||
server.ServerAccount = strings.TrimSpace(self.GetString("server_account"))
|
||||
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.Detail = strings.TrimSpace(self.GetString("detail"))
|
||||
server.Password = strings.TrimSpace(self.GetString("password"))
|
||||
|
||||
server.Type, _ = self.GetInt("type")
|
||||
server.Port, _ = self.GetInt("port")
|
||||
server.GroupId, _ = self.GetInt("group_id")
|
||||
|
||||
if err := server.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (this *ServerController) Edit() {
|
||||
id, _ := this.GetInt("id")
|
||||
server, err := models.TaskServerGetById(id)
|
||||
func (self *ServerController) AjaxDel() {
|
||||
id, _ := self.GetInt("id")
|
||||
server, _ := models.TaskServerGetById(id)
|
||||
server.UpdateTime = time.Now().Unix()
|
||||
server.Status = 1
|
||||
server.Id = id
|
||||
|
||||
//TODO 查询服务器是否用于定时任务
|
||||
if err := server.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("操作成功", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *ServerController) Table() {
|
||||
//列表
|
||||
page, err := self.GetInt("page")
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
page = 1
|
||||
}
|
||||
limit, err := self.GetInt("limit")
|
||||
if err != nil {
|
||||
limit = 30
|
||||
}
|
||||
serverName := strings.TrimSpace(self.GetString("serverName"))
|
||||
StatusText := []string{
|
||||
"正常",
|
||||
"<font color='red'>禁用</font>",
|
||||
}
|
||||
|
||||
if this.isPost() {
|
||||
server.ServerName = strings.TrimSpace(this.GetString("server_name"))
|
||||
server.ServerAccount = strings.TrimSpace(this.GetString("server_account"))
|
||||
server.ServerIp = strings.TrimSpace(this.GetString("server_ip"))
|
||||
server.Port,_ = strconv.Atoi(this.GetString("port"))
|
||||
server.Type,_ = strconv.Atoi(this.GetString("type"))
|
||||
server.Id,_ = strconv.Atoi(this.GetString("id"))
|
||||
server.PrivateKeySrc = strings.TrimSpace(this.GetString("private_key_src"))
|
||||
server.PublicKeySrc = strings.TrimSpace(this.GetString("public_key_src"))
|
||||
server.Password = strings.TrimSpace(this.GetString("password"))
|
||||
server.Detail = strings.TrimSpace(this.GetString("detail"))
|
||||
server.UpdateTime = time.Now().Unix()
|
||||
server.Status = 0
|
||||
err := server.Update()
|
||||
if err != nil {
|
||||
this.ajaxMsg(err.Error(), MSG_ERR)
|
||||
loginType := [2]string{
|
||||
"密码",
|
||||
"密钥",
|
||||
}
|
||||
|
||||
serverGroup := serverGroupLists(self.serverGroups, self.userId)
|
||||
|
||||
self.pageSize = limit
|
||||
//查询条件
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "status", 0)
|
||||
if self.userId != 1 {
|
||||
groups := strings.Split(self.serverGroups, ",")
|
||||
|
||||
groupsIds := make([]int, 0)
|
||||
for _, v := range groups {
|
||||
id, _ := strconv.Atoi(v)
|
||||
groupsIds = append(groupsIds, id)
|
||||
}
|
||||
this.ajaxMsg("", MSG_OK)
|
||||
filters = append(filters, "group_id__in", groupsIds)
|
||||
}
|
||||
if serverName != "" {
|
||||
filters = append(filters, "server_name__icontains", serverName)
|
||||
}
|
||||
result, count := models.TaskServerGetList(page, self.pageSize, filters...)
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["server_name"] = v.ServerName
|
||||
row["detail"] = v.Detail
|
||||
if serverGroup[v.GroupId] == "" {
|
||||
v.GroupId = 0
|
||||
}
|
||||
row["group_name"] = serverGroup[v.GroupId]
|
||||
row["type"] = loginType[v.Type]
|
||||
row["status"] = v.Status
|
||||
row["status_text"] = StatusText[v.Status]
|
||||
list[k] = row
|
||||
}
|
||||
|
||||
this.Data["pageTitle"] = "编辑服务器"
|
||||
this.Data["server"] = server
|
||||
this.display()
|
||||
}
|
||||
|
||||
//TODO删除更新
|
||||
func (this *ServerController) Batch() {
|
||||
action := this.GetString("action")
|
||||
ids := this.GetStrings("ids")
|
||||
if len(ids) < 1 {
|
||||
this.ajaxMsg("请选择要操作的项目", MSG_ERR)
|
||||
}
|
||||
|
||||
for _, v := range ids {
|
||||
id, _ := strconv.Atoi(v)
|
||||
if id < 1 {
|
||||
continue
|
||||
}
|
||||
switch action {
|
||||
case "delete":
|
||||
//查询服务器是否被占用
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "server_id", id)
|
||||
_, count := models.TaskGetList(1, 1000, filters...)
|
||||
if count > 0 {
|
||||
this.ajaxMsg("请先解除该服务器的任务占用", MSG_ERR)
|
||||
}else{
|
||||
models.TaskServerDelById(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.ajaxMsg("", MSG_OK)
|
||||
self.ajaxList("成功", MSG_OK, count, list)
|
||||
}
|
||||
|
||||
140
controllers/server_group.go
Normal file
@@ -0,0 +1,140 @@
|
||||
/************************************************************
|
||||
** @Description: controllers
|
||||
** @Author: haodaquan
|
||||
** @Date: 2018-06-08 21:57
|
||||
** @Last Modified by: haodaquan
|
||||
** @Last Modified time: 2018-06-08 21:57
|
||||
*************************************************************/
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ServerGroupController struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
func (self *ServerGroupController) List() {
|
||||
self.Data["pageTitle"] = "资源分组管理"
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *ServerGroupController) Add() {
|
||||
self.Data["pageTitle"] = "新增分组"
|
||||
self.display()
|
||||
}
|
||||
func (self *ServerGroupController) Edit() {
|
||||
self.Data["pageTitle"] = "编辑分组"
|
||||
|
||||
id, _ := self.GetInt("id", 0)
|
||||
group, _ := models.TaskGroupGetById(id)
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = group.Id
|
||||
row["group_name"] = group.GroupName
|
||||
row["description"] = group.Description
|
||||
self.Data["group"] = row
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *ServerGroupController) AjaxSave() {
|
||||
servergroup := new(models.ServerGroup)
|
||||
servergroup.GroupName = strings.TrimSpace(self.GetString("group_name"))
|
||||
servergroup.Description = strings.TrimSpace(self.GetString("description"))
|
||||
servergroup.Status = 1
|
||||
|
||||
servergroup_id, _ := self.GetInt("id")
|
||||
|
||||
fmt.Println(servergroup_id)
|
||||
if servergroup_id == 0 {
|
||||
//新增
|
||||
servergroup.CreateTime = time.Now().Unix()
|
||||
servergroup.UpdateTime = time.Now().Unix()
|
||||
servergroup.CreateId = self.userId
|
||||
servergroup.UpdateId = self.userId
|
||||
if _, err := models.ServerGroupAdd(servergroup); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
//修改
|
||||
servergroup.Id = servergroup_id
|
||||
servergroup.UpdateTime = time.Now().Unix()
|
||||
servergroup.UpdateId = self.userId
|
||||
if err := servergroup.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *ServerGroupController) AjaxDel() {
|
||||
|
||||
group_id, _ := self.GetInt("id")
|
||||
group, _ := models.TaskGroupGetById(group_id)
|
||||
group.Status = 0
|
||||
group.Id = group_id
|
||||
group.UpdateTime = time.Now().Unix()
|
||||
//TODO 如果分组下有服务器 需要处理
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "group_id", group_id)
|
||||
filters = append(filters, "status", 0)
|
||||
_, n := models.TaskServerGetList(1, 1, filters...)
|
||||
if n > 0 {
|
||||
self.ajaxMsg("分组下有服务器资源,请先处理", MSG_ERR)
|
||||
}
|
||||
if err := group.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *ServerGroupController) Table() {
|
||||
//列表
|
||||
page, err := self.GetInt("page")
|
||||
if err != nil {
|
||||
page = 1
|
||||
}
|
||||
limit, err := self.GetInt("limit")
|
||||
if err != nil {
|
||||
limit = 30
|
||||
}
|
||||
|
||||
groupName := strings.TrimSpace(self.GetString("groupName"))
|
||||
self.pageSize = limit
|
||||
//查询条件
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "status", 1)
|
||||
|
||||
if self.userId != 1 {
|
||||
groups := strings.Split(self.serverGroups, ",")
|
||||
|
||||
groupsIds := make([]int, 0)
|
||||
for _, v := range groups {
|
||||
id, _ := strconv.Atoi(v)
|
||||
groupsIds = append(groupsIds, id)
|
||||
}
|
||||
filters = append(filters, "id__in", groupsIds)
|
||||
}
|
||||
if groupName != "" {
|
||||
filters = append(filters, "group_name__contains", groupName)
|
||||
}
|
||||
result, count := models.ServerGroupGetList(page, self.pageSize, filters...)
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["group_name"] = v.GroupName
|
||||
row["description"] = v.Description
|
||||
row["create_time"] = beego.Date(time.Unix(v.CreateTime, 0), "Y-m-d H:i:s")
|
||||
row["update_time"] = beego.Date(time.Unix(v.UpdateTime, 0), "Y-m-d H:i:s")
|
||||
list[k] = row
|
||||
}
|
||||
self.ajaxList("成功", MSG_OK, count, list)
|
||||
}
|
||||
@@ -1,76 +1,477 @@
|
||||
/*
|
||||
* @Author: haodaquan
|
||||
* @Date: 2017-06-21 10:22:29
|
||||
* @Last Modified by: haodaquan
|
||||
* @Last Modified time: 2017-06-23 11:04:54
|
||||
*/
|
||||
|
||||
/************************************************************
|
||||
** @Description: controllers
|
||||
** @Author: haodaquan
|
||||
** @Date: 2018-06-11 21:11
|
||||
** @Last Modified by: haodaquan
|
||||
** @Last Modified time: 2018-06-11 21:11
|
||||
*************************************************************/
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/jobs"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"github.com/robfig/cron"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
crons "github.com/george518/PPGo_Job/crons"
|
||||
"github.com/george518/PPGo_Job/jobs"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
)
|
||||
|
||||
type TaskController struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
// 任务列表
|
||||
func (this *TaskController) List() {
|
||||
page, _ := this.GetInt("page")
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
groupId, _ := this.GetInt("groupid")
|
||||
if groupId > 0 {
|
||||
this.Ctx.SetCookie("groupid", strconv.Itoa(groupId)+"|job")
|
||||
} else {
|
||||
arr := strings.Split(this.Ctx.GetCookie("groupid"), "|")
|
||||
groupId, _ = strconv.Atoi(arr[0])
|
||||
func (self *TaskController) List() {
|
||||
self.Data["pageTitle"] = "任务管理"
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *TaskController) AuditList() {
|
||||
self.Data["pageTitle"] = "任务审核"
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *TaskController) Add() {
|
||||
self.Data["pageTitle"] = "新增任务"
|
||||
self.Data["taskGroup"] = taskGroupLists(self.taskGroups, self.userId)
|
||||
self.Data["serverGroup"] = serverLists(self.serverGroups, self.userId)
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *TaskController) Edit() {
|
||||
self.Data["pageTitle"] = "编辑任务"
|
||||
|
||||
id, _ := self.GetInt("id")
|
||||
task, err := models.TaskGetById(id)
|
||||
if err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
|
||||
filters := make([]interface{}, 0)
|
||||
if groupId > 0 && groupId != 99 {
|
||||
filters = append(filters, "group_id", groupId)
|
||||
if task.Status == 1 {
|
||||
self.ajaxMsg("运行状态无法编辑任务,请先暂停任务", MSG_ERR)
|
||||
}
|
||||
result, count := models.TaskGetList(page, this.pageSize, filters...)
|
||||
self.Data["task"] = task
|
||||
|
||||
// 分组列表
|
||||
self.Data["taskGroup"] = taskGroupLists(self.taskGroups, self.userId)
|
||||
self.Data["serverGroup"] = serverLists(self.serverGroups, self.userId)
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *TaskController) Copy() {
|
||||
self.Data["pageTitle"] = "复制任务"
|
||||
|
||||
id, _ := self.GetInt("id")
|
||||
task, err := models.TaskGetById(id)
|
||||
if err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.Data["task"] = task
|
||||
|
||||
// 分组列表
|
||||
self.Data["taskGroup"] = taskGroupLists(self.taskGroups, self.userId)
|
||||
self.Data["serverGroup"] = serverLists(self.serverGroups, self.userId)
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *TaskController) Detail() {
|
||||
self.Data["pageTitle"] = "任务详细"
|
||||
|
||||
id, _ := self.GetInt("id")
|
||||
task, err := models.TaskGetById(id)
|
||||
if err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
|
||||
TextStatus := []string{
|
||||
"<font color='red'><i class='fa fa-minus-square'></i> 暂停</font>",
|
||||
"<font color='green'><i class='fa fa-check-square'></i> 运行中</font>",
|
||||
"<font color='orange'><i class='fa fa-question-circle'></i> 待审核</font>",
|
||||
"<font color='red'><i class='fa fa-times-circle'></i> 审核失败</font>",
|
||||
}
|
||||
|
||||
self.Data["TextStatus"] = TextStatus[task.Status]
|
||||
self.Data["CreateTime"] = beego.Date(time.Unix(task.CreateTime, 0), "Y-m-d H:i:s")
|
||||
self.Data["UpdateTime"] = beego.Date(time.Unix(task.UpdateTime, 0), "Y-m-d H:i:s")
|
||||
self.Data["task"] = task
|
||||
// 分组列表
|
||||
self.Data["taskGroup"] = taskGroupLists(self.taskGroups, self.userId)
|
||||
|
||||
serverName := "本地服务器"
|
||||
if task.ServerId == 0 {
|
||||
serverName = "本地服务器"
|
||||
} else {
|
||||
server, err := models.TaskServerGetById(task.ServerId)
|
||||
if err == nil {
|
||||
serverName = server.ServerName
|
||||
}
|
||||
}
|
||||
self.Data["serverName"] = serverName
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *TaskController) AjaxSave() {
|
||||
task_id, _ := self.GetInt("id")
|
||||
if task_id == 0 {
|
||||
task := new(models.Task)
|
||||
task.CreateId = self.userId
|
||||
task.GroupId, _ = self.GetInt("group_id")
|
||||
task.TaskName = strings.TrimSpace(self.GetString("task_name"))
|
||||
task.Description = strings.TrimSpace(self.GetString("description"))
|
||||
task.Concurrent, _ = self.GetInt("concurrent")
|
||||
task.ServerId, _ = self.GetInt("server_id")
|
||||
task.CronSpec = strings.TrimSpace(self.GetString("cron_spec"))
|
||||
task.Command = strings.TrimSpace(self.GetString("command"))
|
||||
task.Timeout, _ = self.GetInt("timeout")
|
||||
|
||||
msg, isBan := checkCommand(task.Command)
|
||||
if !isBan {
|
||||
self.ajaxMsg("含有禁止命令:"+msg, MSG_ERR)
|
||||
}
|
||||
|
||||
task.CreateTime = time.Now().Unix()
|
||||
task.UpdateTime = time.Now().Unix()
|
||||
task.Status = 2 //审核中
|
||||
|
||||
if task.TaskName == "" || task.CronSpec == "" || task.Command == "" {
|
||||
self.ajaxMsg("请填写完整信息", MSG_ERR)
|
||||
}
|
||||
if _, err := cron.Parse(task.CronSpec); err != nil {
|
||||
self.ajaxMsg("cron表达式无效", MSG_ERR)
|
||||
}
|
||||
if _, err := models.TaskAdd(task); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
task, _ := models.TaskGetById(task_id)
|
||||
//修改
|
||||
task.Id = task_id
|
||||
task.UpdateTime = time.Now().Unix()
|
||||
task.TaskName = strings.TrimSpace(self.GetString("task_name"))
|
||||
task.Description = strings.TrimSpace(self.GetString("description"))
|
||||
task.GroupId, _ = self.GetInt("group_id")
|
||||
task.Concurrent, _ = self.GetInt("concurrent")
|
||||
task.ServerId, _ = self.GetInt("server_id")
|
||||
task.CronSpec = strings.TrimSpace(self.GetString("cron_spec"))
|
||||
task.Command = strings.TrimSpace(self.GetString("command"))
|
||||
task.Timeout, _ = self.GetInt("timeout")
|
||||
task.UpdateId = self.userId
|
||||
task.Status = 2 //审核中
|
||||
|
||||
msg, isBan := checkCommand(task.Command)
|
||||
if !isBan {
|
||||
self.ajaxMsg("含有禁止命令:"+msg, MSG_ERR)
|
||||
}
|
||||
|
||||
if err := task.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
//检查是否含有禁用命令
|
||||
func checkCommand(command string) (string, bool) {
|
||||
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "status", 0)
|
||||
ban, _ := models.BanGetList(1, 1000, filters...)
|
||||
for _, v := range ban {
|
||||
if strings.Contains(command, v.Code) {
|
||||
return v.Code, false
|
||||
}
|
||||
}
|
||||
return "", true
|
||||
}
|
||||
|
||||
func (self *TaskController) AjaxAudit() {
|
||||
|
||||
taskId, _ := self.GetInt("id")
|
||||
if taskId == 0 {
|
||||
self.ajaxMsg("任务不存在", MSG_ERR)
|
||||
}
|
||||
res := changeStatus(taskId, 0, self.userId)
|
||||
if !res {
|
||||
self.ajaxMsg("审核失败", MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *TaskController) AjaxNopass() {
|
||||
taskId, _ := self.GetInt("id")
|
||||
if taskId == 0 {
|
||||
self.ajaxMsg("任务不存在", MSG_ERR)
|
||||
}
|
||||
res := changeStatus(taskId, 3, self.userId)
|
||||
if !res {
|
||||
self.ajaxMsg("操作失败", MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *TaskController) AjaxStart() {
|
||||
taskId, _ := self.GetInt("id")
|
||||
if taskId == 0 {
|
||||
self.ajaxMsg("任务不存在", MSG_ERR)
|
||||
}
|
||||
|
||||
task, err := models.TaskGetById(taskId)
|
||||
if err != nil {
|
||||
self.ajaxMsg("查不到该任务", MSG_ERR)
|
||||
}
|
||||
|
||||
if task.Status != 0 {
|
||||
self.ajaxMsg("任务状态有误", MSG_ERR)
|
||||
}
|
||||
|
||||
job, err := jobs.NewJobFromTask(task)
|
||||
if err != nil {
|
||||
self.ajaxMsg("创建任务失败", MSG_ERR)
|
||||
}
|
||||
|
||||
if jobs.AddJob(task.CronSpec, job) {
|
||||
task.Status = 1
|
||||
task.Update()
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *TaskController) AjaxPause() {
|
||||
taskId, _ := self.GetInt("id")
|
||||
if taskId == 0 {
|
||||
self.ajaxMsg("任务不存在", MSG_ERR)
|
||||
}
|
||||
|
||||
task, err := models.TaskGetById(taskId)
|
||||
if err != nil {
|
||||
self.ajaxMsg("查不到该任务", MSG_ERR)
|
||||
}
|
||||
|
||||
jobs.RemoveJob(taskId)
|
||||
task.Status = 0
|
||||
task.Update()
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
|
||||
}
|
||||
|
||||
// 立即执行
|
||||
func (self *TaskController) AjaxRun() {
|
||||
id, _ := self.GetInt("id")
|
||||
task, err := models.TaskGetById(id)
|
||||
if err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
|
||||
job, err := jobs.NewJobFromTask(task)
|
||||
if err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
job.Run()
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *TaskController) AjaxBatchStart() {
|
||||
idArr := self.GetStrings("ids")
|
||||
ids := strings.Split(idArr[0], ",")
|
||||
if len(ids) < 1 {
|
||||
self.ajaxMsg("请选择要操作的任务", MSG_ERR)
|
||||
}
|
||||
for _, v := range ids {
|
||||
id, _ := strconv.Atoi(v)
|
||||
if id < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
if task, err := models.TaskGetById(id); err == nil {
|
||||
job, err := jobs.NewJobFromTask(task)
|
||||
if err == nil {
|
||||
jobs.AddJob(task.CronSpec, job)
|
||||
task.Status = 1
|
||||
task.Update()
|
||||
}
|
||||
}
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *TaskController) AjaxBatchPause() {
|
||||
idArr := self.GetStrings("ids")
|
||||
ids := strings.Split(idArr[0], ",")
|
||||
if len(ids) < 1 {
|
||||
self.ajaxMsg("请选择要操作的任务", MSG_ERR)
|
||||
}
|
||||
for _, v := range ids {
|
||||
id, _ := strconv.Atoi(v)
|
||||
if id < 1 {
|
||||
continue
|
||||
}
|
||||
jobs.RemoveJob(id)
|
||||
|
||||
if task, err := models.TaskGetById(id); err == nil {
|
||||
task.Status = 0
|
||||
task.Update()
|
||||
}
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *TaskController) AjaxBatchDel() {
|
||||
idArr := self.GetStrings("ids")
|
||||
ids := strings.Split(idArr[0], ",")
|
||||
if len(ids) < 1 {
|
||||
self.ajaxMsg("请选择要操作的任务", MSG_ERR)
|
||||
}
|
||||
for _, v := range ids {
|
||||
id, _ := strconv.Atoi(v)
|
||||
if id < 1 {
|
||||
continue
|
||||
}
|
||||
models.TaskDel(id)
|
||||
models.TaskLogDelByTaskId(id)
|
||||
jobs.RemoveJob(id)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *TaskController) AjaxBatchAudit() {
|
||||
idArr := self.GetStrings("ids")
|
||||
ids := strings.Split(idArr[0], ",")
|
||||
if len(ids) < 1 {
|
||||
self.ajaxMsg("请选择要操作的任务", MSG_ERR)
|
||||
}
|
||||
for _, v := range ids {
|
||||
id, _ := strconv.Atoi(v)
|
||||
if id < 1 {
|
||||
continue
|
||||
}
|
||||
changeStatus(id, 0, self.userId)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *TaskController) AjaxBatchNoPass() {
|
||||
idArr := self.GetStrings("ids")
|
||||
ids := strings.Split(idArr[0], ",")
|
||||
if len(ids) < 1 {
|
||||
self.ajaxMsg("请选择要操作的任务", MSG_ERR)
|
||||
}
|
||||
for _, v := range ids {
|
||||
id, _ := strconv.Atoi(v)
|
||||
if id < 1 {
|
||||
continue
|
||||
}
|
||||
changeStatus(id, 3, self.userId)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func changeStatus(taskId, status, userId int) bool {
|
||||
|
||||
if taskId == 0 {
|
||||
return false
|
||||
}
|
||||
task, _ := models.TaskGetById(taskId)
|
||||
//修改
|
||||
task.Id = taskId
|
||||
task.UpdateTime = time.Now().Unix()
|
||||
task.UpdateId = userId
|
||||
task.Status = status //0,1,2,3,9
|
||||
|
||||
if err := task.Update(); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *TaskController) AjaxDel() {
|
||||
id, _ := self.GetInt("id")
|
||||
task, _ := models.TaskGetById(id)
|
||||
|
||||
task.UpdateTime = time.Now().Unix()
|
||||
task.UpdateId = self.userId
|
||||
task.Status = -1
|
||||
task.Id = id
|
||||
|
||||
//TODO 查询服务器是否用于定时任务
|
||||
if err := task.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("操作成功", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *TaskController) Table() {
|
||||
//列表
|
||||
page, err := self.GetInt("page")
|
||||
if err != nil {
|
||||
page = 1
|
||||
}
|
||||
limit, err := self.GetInt("limit")
|
||||
if err != nil {
|
||||
limit = 30
|
||||
}
|
||||
|
||||
status, _ := self.GetInt("status")
|
||||
|
||||
taskName := strings.TrimSpace(self.GetString("taskName"))
|
||||
StatusText := []string{
|
||||
"<font color='red'><i class='fa fa-minus-square'></i></font>",
|
||||
"<font color='green'><i class='fa fa-check-square'></i></font>",
|
||||
"<font color='orange'><i class='fa fa-question-circle'></i></font>",
|
||||
"<font color='red'><i class='fa fa-times-circle'></i></font>",
|
||||
}
|
||||
|
||||
taskGroup := taskGroupLists(self.taskGroups, self.userId)
|
||||
self.pageSize = limit
|
||||
//查询条件
|
||||
filters := make([]interface{}, 0)
|
||||
|
||||
if status == 2 {
|
||||
//审核中,审核失败
|
||||
ids := []int{2, 3}
|
||||
filters = append(filters, "status__in", ids)
|
||||
} else {
|
||||
ids := []int{0, 1}
|
||||
filters = append(filters, "status__in", ids)
|
||||
}
|
||||
|
||||
if self.userId != 1 {
|
||||
groups := strings.Split(self.taskGroups, ",")
|
||||
|
||||
groupsIds := make([]int, 0)
|
||||
for _, v := range groups {
|
||||
id, _ := strconv.Atoi(v)
|
||||
groupsIds = append(groupsIds, id)
|
||||
}
|
||||
filters = append(filters, "group_id__in", groupsIds)
|
||||
}
|
||||
|
||||
if taskName != "" {
|
||||
filters = append(filters, "task_name__icontains", taskName)
|
||||
}
|
||||
result, count := models.TaskGetList(page, self.pageSize, filters...)
|
||||
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
|
||||
// 分组列表
|
||||
groups, _ := models.TaskGroupGetList(1, 100)
|
||||
groups_map := make(map[int]string)
|
||||
for _, gname := range groups {
|
||||
groups_map[gname.Id] = gname.GroupName
|
||||
}
|
||||
|
||||
//服务器列表
|
||||
servers, _ := models.TaskServerGetList(1, 100)
|
||||
|
||||
server_map := make(map[int]string)
|
||||
for _, sname := range servers {
|
||||
server_map[sname.Id] = sname.ServerName
|
||||
}
|
||||
server_map[0] = "本地"
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["name"] = v.TaskName
|
||||
row["cron_spec"] = v.CronSpec
|
||||
row["status"] = v.Status
|
||||
|
||||
groupName := "默认分组"
|
||||
|
||||
if name, ok := taskGroup[v.GroupId]; ok {
|
||||
groupName = name
|
||||
}
|
||||
|
||||
row["group_name"] = groupName
|
||||
row["task_name"] = StatusText[v.Status] + " " + groupName + "-" + " " + v.TaskName
|
||||
row["description"] = v.Description
|
||||
row["group_id"] = v.GroupId
|
||||
row["group_name"] = groups_map[v.GroupId]
|
||||
row["server_name"] = server_map[v.ServerId]
|
||||
row["is_odd"] = k % 2
|
||||
|
||||
//row["status_text"] = StatusText[v.Status]
|
||||
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
|
||||
|
||||
e := jobs.GetEntryById(v.Id)
|
||||
if e != nil {
|
||||
@@ -91,305 +492,9 @@ func (this *TaskController) List() {
|
||||
}
|
||||
row["running"] = 0
|
||||
}
|
||||
|
||||
list[k] = row
|
||||
}
|
||||
|
||||
this.Data["pageTitle"] = "任务列表"
|
||||
this.Data["list"] = list
|
||||
this.Data["groups"] = groups
|
||||
this.Data["groupid"] = groupId
|
||||
this.Data["pageBar"] = libs.NewPager(page, int(count), this.pageSize, beego.URLFor("TaskController.List", "groupid", groupId), true).ToString()
|
||||
this.display()
|
||||
}
|
||||
|
||||
// 添加任务
|
||||
func (this *TaskController) Add() {
|
||||
|
||||
if this.isPost() {
|
||||
task := new(models.Task)
|
||||
task.UserId = this.userId
|
||||
task.GroupId, _ = this.GetInt("group_id")
|
||||
task.TaskName = strings.TrimSpace(this.GetString("task_name"))
|
||||
task.Description = strings.TrimSpace(this.GetString("description"))
|
||||
task.Concurrent, _ = this.GetInt("concurrent")
|
||||
task.ServerId, _ = this.GetInt("server_id")
|
||||
task.CronSpec = strings.TrimSpace(this.GetString("cron_spec"))
|
||||
task.Command = strings.TrimSpace(this.GetString("command"))
|
||||
task.Timeout, _ = this.GetInt("timeout")
|
||||
|
||||
if task.TaskName == "" || task.CronSpec == "" || task.Command == "" {
|
||||
this.ajaxMsg("请填写完整信息", MSG_ERR)
|
||||
}
|
||||
if _, err := crons.Parse(task.CronSpec); err != nil {
|
||||
this.ajaxMsg("cron表达式无效", MSG_ERR)
|
||||
}
|
||||
if _, err := models.TaskAdd(task); err != nil {
|
||||
this.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
|
||||
this.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
// 分组列表
|
||||
groups, _ := models.TaskGroupGetList(1, 100)
|
||||
this.Data["groups"] = groups
|
||||
//服务器分组
|
||||
servers, _ := models.TaskServerGetList(1, 100)
|
||||
this.Data["servers"] = servers
|
||||
this.Data["pageTitle"] = "添加任务"
|
||||
this.display()
|
||||
}
|
||||
|
||||
// 编辑任务
|
||||
func (this *TaskController) Edit() {
|
||||
id, _ := this.GetInt("id")
|
||||
|
||||
task, err := models.TaskGetById(id)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
if task.Status != 0 {
|
||||
this.ajaxMsg("激活状态无法编辑任务,请先暂停任务", MSG_ERR)
|
||||
}
|
||||
|
||||
if this.isPost() {
|
||||
task.TaskName = strings.TrimSpace(this.GetString("task_name"))
|
||||
task.Description = strings.TrimSpace(this.GetString("description"))
|
||||
task.GroupId, _ = this.GetInt("group_id")
|
||||
task.Concurrent, _ = this.GetInt("concurrent")
|
||||
task.ServerId, _ = this.GetInt("server_id")
|
||||
task.CronSpec = strings.TrimSpace(this.GetString("cron_spec"))
|
||||
task.Command = strings.TrimSpace(this.GetString("command"))
|
||||
task.Timeout, _ = this.GetInt("timeout")
|
||||
if task.TaskName == "" || task.CronSpec == "" || task.Command == "" {
|
||||
this.ajaxMsg("请填写完整信息", MSG_ERR)
|
||||
}
|
||||
if _, err := crons.Parse(task.CronSpec); err != nil {
|
||||
this.ajaxMsg("cron表达式无效", MSG_ERR)
|
||||
}
|
||||
if err := task.Update(); err != nil {
|
||||
this.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
|
||||
this.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
// 分组列表
|
||||
groups, _ := models.TaskGroupGetList(1, 100)
|
||||
this.Data["groups"] = groups
|
||||
//服务器分组
|
||||
servers, _ := models.TaskServerGetList(1, 100)
|
||||
this.Data["servers"] = servers
|
||||
|
||||
this.Data["task"] = task
|
||||
this.Data["pageTitle"] = "编辑任务"
|
||||
this.display()
|
||||
}
|
||||
|
||||
//复制任务
|
||||
func (this *TaskController) Copy() {
|
||||
|
||||
id, _ := this.GetInt("id")
|
||||
task, err := models.TaskGetById(id)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
// 分组列表
|
||||
groups, _ := models.TaskGroupGetList(1, 100)
|
||||
this.Data["groups"] = groups
|
||||
//服务器分组
|
||||
servers, _ := models.TaskServerGetList(1, 100)
|
||||
this.Data["servers"] = servers
|
||||
|
||||
this.Data["task"] = task
|
||||
this.Data["pageTitle"] = "复制任务"
|
||||
this.display()
|
||||
}
|
||||
|
||||
// 任务执行日志列表
|
||||
func (this *TaskController) Logs() {
|
||||
taskId, _ := this.GetInt("id")
|
||||
page, _ := this.GetInt("page")
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
task, err := models.TaskGetById(taskId)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
result, count := models.TaskLogGetList(page, this.pageSize, "task_id", task.Id)
|
||||
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["start_time"] = beego.Date(time.Unix(v.CreateTime, 0), "Y-m-d H:i:s")
|
||||
row["process_time"] = float64(v.ProcessTime) / 1000
|
||||
row["ouput_size"] = libs.SizeFormat(float64(len(v.Output)))
|
||||
row["status"] = v.Status
|
||||
list[k] = row
|
||||
}
|
||||
|
||||
this.Data["pageTitle"] = "任务执行日志"
|
||||
this.Data["list"] = list
|
||||
this.Data["task"] = task
|
||||
this.Data["pageBar"] = libs.NewPager(page, int(count), this.pageSize, beego.URLFor("TaskController.Logs", "id", taskId), true).ToString()
|
||||
this.display()
|
||||
}
|
||||
|
||||
// 查看日志详情
|
||||
func (this *TaskController) ViewLog() {
|
||||
id, _ := this.GetInt("id")
|
||||
|
||||
taskLog, err := models.TaskLogGetById(id)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
task, err := models.TaskGetById(taskLog.TaskId)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
data := make(map[string]interface{})
|
||||
data["id"] = taskLog.Id
|
||||
data["output"] = taskLog.Output
|
||||
data["error"] = taskLog.Error
|
||||
data["start_time"] = beego.Date(time.Unix(taskLog.CreateTime, 0), "Y-m-d H:i:s")
|
||||
data["process_time"] = float64(taskLog.ProcessTime) / 1000
|
||||
data["ouput_size"] = libs.SizeFormat(float64(len(taskLog.Output)))
|
||||
data["status"] = taskLog.Status
|
||||
|
||||
this.Data["task"] = task
|
||||
this.Data["data"] = data
|
||||
this.Data["pageTitle"] = "查看日志"
|
||||
this.display()
|
||||
}
|
||||
|
||||
// 批量操作日志
|
||||
func (this *TaskController) LogBatch() {
|
||||
action := this.GetString("action")
|
||||
ids := this.GetStrings("ids")
|
||||
if len(ids) < 1 {
|
||||
this.ajaxMsg("请选择要操作的项目", MSG_ERR)
|
||||
}
|
||||
for _, v := range ids {
|
||||
id, _ := strconv.Atoi(v)
|
||||
if id < 1 {
|
||||
continue
|
||||
}
|
||||
switch action {
|
||||
case "delete":
|
||||
models.TaskLogDelById(id)
|
||||
}
|
||||
}
|
||||
|
||||
this.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
// 批量操作
|
||||
func (this *TaskController) Batch() {
|
||||
action := this.GetString("action")
|
||||
ids := this.GetStrings("ids")
|
||||
if len(ids) < 1 {
|
||||
this.ajaxMsg("请选择要操作的项目", MSG_ERR)
|
||||
}
|
||||
|
||||
for _, v := range ids {
|
||||
id, _ := strconv.Atoi(v)
|
||||
if id < 1 {
|
||||
continue
|
||||
}
|
||||
switch action {
|
||||
case "active":
|
||||
if task, err := models.TaskGetById(id); err == nil {
|
||||
job, err := jobs.NewJobFromTask(task)
|
||||
if err == nil {
|
||||
jobs.AddJob(task.CronSpec, job)
|
||||
task.Status = 1
|
||||
task.Update()
|
||||
}
|
||||
}
|
||||
case "pause":
|
||||
jobs.RemoveJob(id)
|
||||
|
||||
if task, err := models.TaskGetById(id); err == nil {
|
||||
task.Status = 0
|
||||
task.Update()
|
||||
}
|
||||
|
||||
case "delete":
|
||||
models.TaskDel(id)
|
||||
models.TaskLogDelByTaskId(id)
|
||||
jobs.RemoveJob(id)
|
||||
}
|
||||
}
|
||||
|
||||
this.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
// 启动任务
|
||||
func (this *TaskController) Start() {
|
||||
id, _ := this.GetInt("id")
|
||||
|
||||
task, err := models.TaskGetById(id)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
job, err := jobs.NewJobFromTask(task)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
if jobs.AddJob(task.CronSpec, job) {
|
||||
task.Status = 1
|
||||
task.Update()
|
||||
}
|
||||
|
||||
refer := this.Ctx.Request.Referer()
|
||||
if refer == "" {
|
||||
refer = beego.URLFor("TaskController.List")
|
||||
}
|
||||
this.redirect(refer)
|
||||
}
|
||||
|
||||
// 暂停任务
|
||||
func (this *TaskController) Pause() {
|
||||
id, _ := this.GetInt("id")
|
||||
|
||||
task, err := models.TaskGetById(id)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
jobs.RemoveJob(id)
|
||||
task.Status = 0
|
||||
task.Update()
|
||||
|
||||
refer := this.Ctx.Request.Referer()
|
||||
if refer == "" {
|
||||
refer = beego.URLFor("TaskController.List")
|
||||
}
|
||||
this.redirect(refer)
|
||||
}
|
||||
|
||||
// 立即执行
|
||||
func (this *TaskController) Run() {
|
||||
id, _ := this.GetInt("id")
|
||||
|
||||
task, err := models.TaskGetById(id)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
|
||||
job, err := jobs.NewJobFromTask(task)
|
||||
if err != nil {
|
||||
this.showMsg(err.Error())
|
||||
}
|
||||
job.Run()
|
||||
this.redirect(beego.URLFor("TaskController.ViewLog", "id", job.GetLogId()))
|
||||
self.ajaxList("成功", MSG_OK, count, list)
|
||||
}
|
||||
|
||||
140
controllers/task_group.go
Normal file
@@ -0,0 +1,140 @@
|
||||
/************************************************************
|
||||
** @Description: controllers
|
||||
** @Author: haodaquan
|
||||
** @Date: 2018-06-10 22:24
|
||||
** @Last Modified by: haodaquan
|
||||
** @Last Modified time: 2018-06-10 22:24
|
||||
*************************************************************/
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type GroupController struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
func (self *GroupController) List() {
|
||||
self.Data["pageTitle"] = "任务分组管理"
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *GroupController) Add() {
|
||||
self.Data["pageTitle"] = "新增分组"
|
||||
self.display()
|
||||
}
|
||||
func (self *GroupController) Edit() {
|
||||
self.Data["pageTitle"] = "编辑分组"
|
||||
|
||||
id, _ := self.GetInt("id", 0)
|
||||
group, _ := models.GroupGetById(id)
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = group.Id
|
||||
row["group_name"] = group.GroupName
|
||||
row["description"] = group.Description
|
||||
self.Data["group"] = row
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *GroupController) AjaxSave() {
|
||||
group := new(models.Group)
|
||||
group.GroupName = strings.TrimSpace(self.GetString("group_name"))
|
||||
group.Description = strings.TrimSpace(self.GetString("description"))
|
||||
group.Status = 1
|
||||
|
||||
group_id, _ := self.GetInt("id")
|
||||
|
||||
fmt.Println(group_id)
|
||||
if group_id == 0 {
|
||||
//新增
|
||||
group.CreateTime = time.Now().Unix()
|
||||
group.UpdateTime = time.Now().Unix()
|
||||
group.CreateId = self.userId
|
||||
group.UpdateId = self.userId
|
||||
if _, err := models.GroupAdd(group); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
//修改
|
||||
group.Id = group_id
|
||||
group.UpdateTime = time.Now().Unix()
|
||||
group.UpdateId = self.userId
|
||||
if err := group.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *GroupController) AjaxDel() {
|
||||
|
||||
group_id, _ := self.GetInt("id")
|
||||
group, _ := models.GroupGetById(group_id)
|
||||
group.Status = 0
|
||||
group.Id = group_id
|
||||
group.UpdateTime = time.Now().Unix()
|
||||
//TODO 如果分组下有任务 不处理
|
||||
//filters := make([]interface{}, 0)
|
||||
//filters = append(filters, "group_id", group_id)
|
||||
//filters = append(filters, "status", 0)
|
||||
//_, n := models.TaskServerGetList(1, 1, filters...)
|
||||
//if n > 0 {
|
||||
// self.ajaxMsg("分组下有服务器资源,请先处理", MSG_ERR)
|
||||
//}
|
||||
if err := group.Update(); err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||
|
||||
func (self *GroupController) Table() {
|
||||
//列表
|
||||
page, err := self.GetInt("page")
|
||||
if err != nil {
|
||||
page = 1
|
||||
}
|
||||
limit, err := self.GetInt("limit")
|
||||
if err != nil {
|
||||
limit = 30
|
||||
}
|
||||
|
||||
groupName := strings.TrimSpace(self.GetString("groupName"))
|
||||
self.pageSize = limit
|
||||
//查询条件
|
||||
filters := make([]interface{}, 0)
|
||||
filters = append(filters, "status", 1)
|
||||
|
||||
if self.userId != 1 {
|
||||
groups := strings.Split(self.taskGroups, ",")
|
||||
|
||||
groupsIds := make([]int, 0)
|
||||
for _, v := range groups {
|
||||
id, _ := strconv.Atoi(v)
|
||||
groupsIds = append(groupsIds, id)
|
||||
}
|
||||
filters = append(filters, "id__in", groupsIds)
|
||||
}
|
||||
if groupName != "" {
|
||||
filters = append(filters, "group_name__contains", groupName)
|
||||
}
|
||||
result, count := models.GroupGetList(page, self.pageSize, filters...)
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["group_name"] = v.GroupName
|
||||
row["description"] = v.Description
|
||||
row["create_time"] = beego.Date(time.Unix(v.CreateTime, 0), "Y-m-d H:i:s")
|
||||
row["update_time"] = beego.Date(time.Unix(v.UpdateTime, 0), "Y-m-d H:i:s")
|
||||
list[k] = row
|
||||
}
|
||||
self.ajaxList("成功", MSG_OK, count, list)
|
||||
}
|
||||
168
controllers/task_log.go
Normal file
@@ -0,0 +1,168 @@
|
||||
/************************************************************
|
||||
** @Description: controllers
|
||||
** @Author: george hao
|
||||
** @Date: 2018-07-05 16:43
|
||||
** @Last Modified by: george hao
|
||||
** @Last Modified time: 2018-07-05 16:43
|
||||
*************************************************************/
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/george518/PPGo_Job/libs"
|
||||
"github.com/george518/PPGo_Job/models"
|
||||
"strconv"
|
||||
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TaskLogController struct {
|
||||
BaseController
|
||||
}
|
||||
|
||||
func (self *TaskLogController) List() {
|
||||
taskId, err := self.GetInt("task_id")
|
||||
if err != nil {
|
||||
taskId = 1
|
||||
}
|
||||
|
||||
task, err := models.TaskGetById(taskId)
|
||||
if err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
self.Data["pageTitle"] = "日志管理 - " + task.TaskName + "(#" + strconv.Itoa(task.Id) + ")"
|
||||
self.Data["task_id"] = task.Id
|
||||
self.display()
|
||||
}
|
||||
|
||||
func (self *TaskLogController) Table() {
|
||||
//列表
|
||||
page, err := self.GetInt("page")
|
||||
if err != nil {
|
||||
page = 1
|
||||
}
|
||||
limit, err := self.GetInt("limit")
|
||||
if err != nil {
|
||||
limit = 30
|
||||
}
|
||||
|
||||
self.pageSize = limit
|
||||
//查询条件
|
||||
filters := make([]interface{}, 0)
|
||||
taskId, err := self.GetInt("task_id")
|
||||
if err != nil {
|
||||
taskId = 1
|
||||
}
|
||||
|
||||
TextStatus := []string{
|
||||
"<font color='red'><i class='fa fa-times-circle'></i> 错误</font>",
|
||||
"<font color='green'><i class='fa fa-check-square'></i> 正常</font>",
|
||||
}
|
||||
|
||||
Status, err := self.GetInt("status")
|
||||
|
||||
if err == nil && Status != 9 {
|
||||
status := Status + 1
|
||||
filters = append(filters, "status", status)
|
||||
}
|
||||
filters = append(filters, "task_id", taskId)
|
||||
|
||||
result, count := models.TaskLogGetList(page, self.pageSize, filters...)
|
||||
list := make([]map[string]interface{}, len(result))
|
||||
|
||||
for k, v := range result {
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = v.Id
|
||||
row["task_id"] = v.TaskId
|
||||
row["start_time"] = beego.Date(time.Unix(v.CreateTime, 0), "Y-m-d H:i:s")
|
||||
row["process_time"] = float64(v.ProcessTime) / 1000
|
||||
row["ouput_size"] = libs.SizeFormat(float64(len(v.Output)))
|
||||
index := v.Status + 1
|
||||
row["status"] = TextStatus[index]
|
||||
|
||||
list[k] = row
|
||||
}
|
||||
|
||||
self.ajaxList("成功", MSG_OK, count, list)
|
||||
}
|
||||
|
||||
func (self *TaskLogController) Detail() {
|
||||
|
||||
//日志内容
|
||||
id, _ := self.GetInt("id")
|
||||
tasklog, err := models.TaskLogGetById(id)
|
||||
if err != nil {
|
||||
self.Ctx.WriteString("日志不存在")
|
||||
return
|
||||
}
|
||||
LogTextStatus := []string{
|
||||
"<font color='red'><i class='fa fa-times-circle'></i> 错误</font>",
|
||||
"<font color='green'><i class='fa fa-check-square'></i> 正常</font>",
|
||||
}
|
||||
row := make(map[string]interface{})
|
||||
row["id"] = tasklog.Id
|
||||
row["task_id"] = tasklog.TaskId
|
||||
row["start_time"] = beego.Date(time.Unix(tasklog.CreateTime, 0), "Y-m-d H:i:s")
|
||||
row["process_time"] = float64(tasklog.ProcessTime) / 1000
|
||||
row["ouput_size"] = libs.SizeFormat(float64(len(tasklog.Output)))
|
||||
row["ouput"] = tasklog.Output
|
||||
row["error"] = tasklog.Error
|
||||
|
||||
index := tasklog.Status + 1
|
||||
row["status"] = LogTextStatus[index]
|
||||
|
||||
self.Data["taskLog"] = row
|
||||
|
||||
//任务详情
|
||||
task, err := models.TaskGetById(tasklog.TaskId)
|
||||
if err != nil {
|
||||
self.ajaxMsg(err.Error(), MSG_ERR)
|
||||
}
|
||||
TextStatus := []string{
|
||||
"<font color='red'><i class='fa fa-minus-square'></i> 暂停</font>",
|
||||
"<font color='green'><i class='fa fa-check-square'></i> 运行中</font>",
|
||||
"<font color='orange'><i class='fa fa-check-square'></i> 待审核</font>",
|
||||
"<font color='blue'><i class='fa fa-times-circle'></i> 审核失败</font>",
|
||||
}
|
||||
|
||||
self.Data["TextStatus"] = TextStatus[task.Status]
|
||||
self.Data["CreateTime"] = beego.Date(time.Unix(task.CreateTime, 0), "Y-m-d H:i:s")
|
||||
self.Data["UpdateTime"] = beego.Date(time.Unix(task.UpdateTime, 0), "Y-m-d H:i:s")
|
||||
self.Data["task"] = task
|
||||
// 分组列表
|
||||
self.Data["taskGroup"] = taskGroupLists(self.taskGroups, self.userId)
|
||||
|
||||
serverName := "本地服务器"
|
||||
if task.ServerId == 0 {
|
||||
serverName = "本地服务器"
|
||||
} else {
|
||||
server, err := models.TaskServerGetById(task.ServerId)
|
||||
if err == nil {
|
||||
serverName = server.ServerName
|
||||
}
|
||||
}
|
||||
self.Data["serverName"] = serverName
|
||||
self.Data["pageTitle"] = "日志详细" + "(#" + strconv.Itoa(id) + ")"
|
||||
self.display()
|
||||
}
|
||||
|
||||
// 批量操作日志
|
||||
func (self *TaskLogController) AjaxDel() {
|
||||
ids := self.GetStrings("ids")
|
||||
idArr := strings.Split(ids[0], ",")
|
||||
|
||||
if len(idArr) < 1 {
|
||||
self.ajaxMsg("请选择要操作的项目", MSG_ERR)
|
||||
}
|
||||
|
||||
for _, v := range idArr {
|
||||
id, _ := strconv.Atoi(v)
|
||||
if id < 1 {
|
||||
continue
|
||||
}
|
||||
models.TaskLogDelById(id)
|
||||
}
|
||||
|
||||
self.ajaxMsg("", MSG_OK)
|
||||
}
|
||||