v2版本正式上线测试版

This commit is contained in:
george
2018-07-13 17:53:34 +08:00
parent 092ecf605b
commit 7bbe5585d8
661 changed files with 40153 additions and 2053 deletions

18
V1/conf/app.conf Normal file
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,2 @@
[![GoDoc](http://godoc.org/github.com/robfig/cron?status.png)](http://godoc.org/github.com/robfig/cron)
[![Build Status](https://travis-ci.org/robfig/cron.svg?branch=master)](https://travis-ci.org/robfig/cron)

27
V1/crons/constantdelay.go Normal file
View 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)
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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{})
}

View File

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View File

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 165 KiB

View File

Before

Width:  |  Height:  |  Size: 243 KiB

After

Width:  |  Height:  |  Size: 243 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

67
V1/views/group/add.html Normal file
View 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
View 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
View 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
View 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">&times;</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
View 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
View 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
View 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
View 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
View 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
View 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
View 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>

View File

@@ -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
View 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
View 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
View 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)
}

View File

@@ -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
View 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
View 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
View 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)
}

View File

@@ -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
View 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)
}

View File

@@ -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 + "-" + "&nbsp;" + 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
View 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
View 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)
}

Some files were not shown because too many files have changed in this diff Show More