增加可配置的定时任务功能
This commit is contained in:
		| @ -1,6 +1,6 @@ | |||||||
| # Next Terminal | # Next Terminal | ||||||
|  |  | ||||||
| 你的下一个终端。 | 下一代终端。 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -29,6 +29,10 @@ https://next-terminal.typesafe.cn/ | |||||||
|  |  | ||||||
| test/test | test/test | ||||||
|  |  | ||||||
|  | ## 协议与条款 | ||||||
|  |  | ||||||
|  | 如您需要在企业网络中使用 next-terminal,建议先征求 IT 管理员的同意。下载、使用或分发 next-terminal 前,您必须同意 [协议](./LICENSE) 条款与限制。本项目不提供任何担保,亦不承担任何责任。 | ||||||
|  |  | ||||||
| ## 快速安装 | ## 快速安装 | ||||||
|  |  | ||||||
| - [使用docker安装](docs/install-docker.md) | - [使用docker安装](docs/install-docker.md) | ||||||
|  | |||||||
							
								
								
									
										43
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								main.go
									
									
									
									
									
								
							| @ -6,6 +6,7 @@ import ( | |||||||
| 	nested "github.com/antonfisher/nested-logrus-formatter" | 	nested "github.com/antonfisher/nested-logrus-formatter" | ||||||
| 	"github.com/labstack/gommon/log" | 	"github.com/labstack/gommon/log" | ||||||
| 	"github.com/patrickmn/go-cache" | 	"github.com/patrickmn/go-cache" | ||||||
|  | 	"github.com/robfig/cron/v3" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"gorm.io/driver/mysql" | 	"gorm.io/driver/mysql" | ||||||
| 	"gorm.io/driver/sqlite" | 	"gorm.io/driver/sqlite" | ||||||
| @ -155,6 +156,9 @@ func Run() error { | |||||||
| 	if err := global.DB.AutoMigrate(&model.Num{}); err != nil { | 	if err := global.DB.AutoMigrate(&model.Num{}); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 	if err := global.DB.AutoMigrate(&model.Job{}); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if len(model.FindAllTemp()) == 0 { | 	if len(model.FindAllTemp()) == 0 { | ||||||
| 		for i := 0; i <= 30; i++ { | 		for i := 0; i <= 30; i++ { | ||||||
| @ -174,6 +178,45 @@ func Run() error { | |||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
| 	global.Store = global.NewStore() | 	global.Store = global.NewStore() | ||||||
|  | 	global.Cron = cron.New(cron.WithSeconds()) //精确到秒 | ||||||
|  |  | ||||||
|  | 	jobs, err := model.FindJobByFunc(model.FuncCheckAssetStatusJob) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if jobs == nil || len(jobs) == 0 { | ||||||
|  | 		job := model.Job{ | ||||||
|  | 			ID:      utils.UUID(), | ||||||
|  | 			Name:    "资产状态检测", | ||||||
|  | 			Func:    model.FuncCheckAssetStatusJob, | ||||||
|  | 			Cron:    "0 0 0/1 * * ?", | ||||||
|  | 			Status:  model.JobStatusRunning, | ||||||
|  | 			Created: utils.NowJsonTime(), | ||||||
|  | 			Updated: utils.NowJsonTime(), | ||||||
|  | 		} | ||||||
|  | 		if err := model.CreateNewJob(&job); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	jobs, err = model.FindJobByFunc(model.FuncDelTimeoutSessionJob) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if jobs == nil || len(jobs) == 0 { | ||||||
|  | 		job := model.Job{ | ||||||
|  | 			ID:      utils.UUID(), | ||||||
|  | 			Name:    "超时会话检测", | ||||||
|  | 			Func:    model.FuncDelTimeoutSessionJob, | ||||||
|  | 			Cron:    "0 0 0 * * ?", | ||||||
|  | 			Status:  model.JobStatusRunning, | ||||||
|  | 			Created: utils.NowJsonTime(), | ||||||
|  | 			Updated: utils.NowJsonTime(), | ||||||
|  | 		} | ||||||
|  | 		if err := model.CreateNewJob(&job); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	loginLogs, err := model.FindAliveLoginLogs() | 	loginLogs, err := model.FindAliveLoginLogs() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | |||||||
							
								
								
									
										86
									
								
								pkg/api/job.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								pkg/api/job.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,86 @@ | |||||||
|  | package api | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/labstack/echo/v4" | ||||||
|  | 	"next-terminal/pkg/model" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func JobCreateEndpoint(c echo.Context) error { | ||||||
|  | 	var item model.Job | ||||||
|  | 	if err := c.Bind(&item); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := model.CreateNewJob(&item); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return Success(c, "") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func JobPagingEndpoint(c echo.Context) error { | ||||||
|  | 	pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex")) | ||||||
|  | 	pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) | ||||||
|  | 	name := c.QueryParam("name") | ||||||
|  | 	status := c.QueryParam("status") | ||||||
|  |  | ||||||
|  | 	items, total, err := model.FindPageJob(pageIndex, pageSize, name, status) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return Success(c, H{ | ||||||
|  | 		"total": total, | ||||||
|  | 		"items": items, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func JobUpdateEndpoint(c echo.Context) error { | ||||||
|  | 	id := c.Param("id") | ||||||
|  |  | ||||||
|  | 	var item model.Job | ||||||
|  | 	if err := c.Bind(&item); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := model.UpdateJobById(&item, id); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return Success(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func JobChangeStatusEndpoint(c echo.Context) error { | ||||||
|  | 	id := c.Param("id") | ||||||
|  | 	status := c.QueryParam("status") | ||||||
|  | 	if err := model.ChangeJobStatusById(id, status); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return Success(c, "") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func JobDeleteEndpoint(c echo.Context) error { | ||||||
|  | 	ids := c.Param("id") | ||||||
|  |  | ||||||
|  | 	split := strings.Split(ids, ",") | ||||||
|  | 	for i := range split { | ||||||
|  | 		jobId := split[i] | ||||||
|  | 		if err := model.DeleteJobById(jobId); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return Success(c, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func JobGetEndpoint(c echo.Context) error { | ||||||
|  | 	id := c.Param("id") | ||||||
|  |  | ||||||
|  | 	item, err := model.FindJobById(id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return Success(c, item) | ||||||
|  | } | ||||||
| @ -144,6 +144,16 @@ func SetupRoutes() *echo.Echo { | |||||||
| 	e.GET("/overview/counter", OverviewCounterEndPoint) | 	e.GET("/overview/counter", OverviewCounterEndPoint) | ||||||
| 	e.GET("/overview/sessions", OverviewSessionPoint) | 	e.GET("/overview/sessions", OverviewSessionPoint) | ||||||
|  |  | ||||||
|  | 	jobs := e.Group("/jobs", Admin) | ||||||
|  | 	{ | ||||||
|  | 		jobs.POST("", JobCreateEndpoint) | ||||||
|  | 		jobs.GET("/paging", JobPagingEndpoint) | ||||||
|  | 		jobs.PUT("/:id", JobUpdateEndpoint) | ||||||
|  | 		jobs.POST("/:id/change-status", JobChangeStatusEndpoint) | ||||||
|  | 		jobs.DELETE("/:id", JobDeleteEndpoint) | ||||||
|  | 		jobs.GET("/:id", JobGetEndpoint) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return e | 	return e | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ package global | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"github.com/patrickmn/go-cache" | 	"github.com/patrickmn/go-cache" | ||||||
|  | 	"github.com/robfig/cron/v3" | ||||||
| 	"gorm.io/gorm" | 	"gorm.io/gorm" | ||||||
| 	"next-terminal/pkg/config" | 	"next-terminal/pkg/config" | ||||||
| ) | ) | ||||||
| @ -13,3 +14,5 @@ var Cache *cache.Cache | |||||||
| var Config *config.Config | var Config *config.Config | ||||||
|  |  | ||||||
| var Store *TunStore | var Store *TunStore | ||||||
|  |  | ||||||
|  | var Cron *cron.Cron | ||||||
|  | |||||||
| @ -1,81 +1,23 @@ | |||||||
| package handle | package handle | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"github.com/robfig/cron/v3" | 	"next-terminal/pkg/global" | ||||||
| 	"github.com/sirupsen/logrus" |  | ||||||
| 	"log" |  | ||||||
| 	"next-terminal/pkg/guacd" | 	"next-terminal/pkg/guacd" | ||||||
| 	"next-terminal/pkg/model" | 	"next-terminal/pkg/model" | ||||||
| 	"next-terminal/pkg/utils" | 	"next-terminal/pkg/utils" | ||||||
| 	"os" | 	"os" | ||||||
| 	"strconv" |  | ||||||
| 	"time" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func RunTicker() { | func RunTicker() { | ||||||
|  |  | ||||||
| 	c := cron.New(cron.WithSeconds()) //精确到秒 | 	// 每隔一小时删除一次未使用的会话信息 | ||||||
|  | 	_, _ = global.Cron.AddJob("0 0 0/1 * * ?", model.DelUnUsedSessionJob{}) | ||||||
|  | 	// 每隔一小时检测一次资产状态 | ||||||
|  | 	//_, _ = global.Cron.AddJob("0 0 0/1 * * ?", model.CheckAssetStatusJob{}) | ||||||
|  | 	// 每日凌晨删除超过时长限制的会话 | ||||||
|  | 	//_, _ = global.Cron.AddJob("0 0 0 * * ?", model.DelTimeoutSessionJob{}) | ||||||
|  |  | ||||||
| 	_, _ = c.AddFunc("0 0 0/1 * * ?", func() { | 	global.Cron.Start() | ||||||
| 		// 定时任务,每隔一小时删除一次未使用的会话信息 |  | ||||||
| 		sessions, _ := model.FindSessionByStatusIn([]string{model.NoConnect, model.Connecting}) |  | ||||||
| 		if sessions != nil && len(sessions) > 0 { |  | ||||||
| 			now := time.Now() |  | ||||||
| 			for i := range sessions { |  | ||||||
| 				if now.Sub(sessions[i].ConnectedTime.Time) > time.Hour*1 { |  | ||||||
| 					_ = model.DeleteSessionById(sessions[i].ID) |  | ||||||
| 					s := sessions[i].Username + "@" + sessions[i].IP + ":" + strconv.Itoa(sessions[i].Port) |  | ||||||
| 					logrus.Infof("会话「%v」ID「%v」超过1小时未打开,已删除。", s, sessions[i].ID) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		// 每隔一小时检测一次资产是否存活 |  | ||||||
| 		assets, _ := model.FindAllAsset() |  | ||||||
| 		if assets != nil && len(assets) > 0 { |  | ||||||
| 			for i := range assets { |  | ||||||
| 				asset := assets[i] |  | ||||||
| 				active := utils.Tcping(asset.IP, asset.Port) |  | ||||||
| 				model.UpdateAssetActiveById(active, asset.ID) |  | ||||||
| 				logrus.Infof("资产「%v」ID「%v」存活状态检测完成,存活「%v」。", asset.Name, asset.ID, active) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	_, err := c.AddFunc("0 0 0 * * ?", func() { |  | ||||||
| 		// 定时任务 每日凌晨检查超过时长限制的会话 |  | ||||||
| 		property, err := model.FindPropertyByName("session-saved-limit") |  | ||||||
| 		if err != nil { |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		if property.Value == "" || property.Value == "-" { |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		limit, err := strconv.Atoi(property.Value) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		sessions, err := model.FindOutTimeSessions(limit) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if sessions != nil && len(sessions) > 0 { |  | ||||||
| 			var sessionIds []string |  | ||||||
| 			for i := range sessions { |  | ||||||
| 				sessionIds = append(sessionIds, sessions[i].ID) |  | ||||||
| 			} |  | ||||||
| 			err := model.DeleteSessionByIds(sessionIds) |  | ||||||
| 			if err != nil { |  | ||||||
| 				logrus.Errorf("删除离线会话失败 %v", err) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	c.Start() |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func RunDataFix() { | func RunDataFix() { | ||||||
|  | |||||||
							
								
								
									
										229
									
								
								pkg/model/job.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								pkg/model/job.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,229 @@ | |||||||
|  | package model | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"github.com/robfig/cron/v3" | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"next-terminal/pkg/global" | ||||||
|  | 	"next-terminal/pkg/utils" | ||||||
|  | 	"strconv" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	JobStatusRunning    = "running" | ||||||
|  | 	JobStatusNotRunning = "not-running" | ||||||
|  |  | ||||||
|  | 	FuncCheckAssetStatusJob  = "check-asset-status-job" | ||||||
|  | 	FuncDelUnUsedSessionJob  = "del-unused-session-job" | ||||||
|  | 	FuncDelTimeoutSessionJob = "del-timeout-session-job" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Job struct { | ||||||
|  | 	ID      string         `gorm:"primary_key" json:"id"` | ||||||
|  | 	JobId   int            `json:"jobId"` | ||||||
|  | 	Name    string         `json:"name"` | ||||||
|  | 	Func    string         `json:"func"` | ||||||
|  | 	Cron    string         `json:"cron"` | ||||||
|  | 	Status  string         `json:"status"` | ||||||
|  | 	Created utils.JsonTime `json:"created"` | ||||||
|  | 	Updated utils.JsonTime `json:"updated"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Job) TableName() string { | ||||||
|  | 	return "jobs" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func FindPageJob(pageIndex, pageSize int, name, status string) (o []Job, total int64, err error) { | ||||||
|  | 	job := Job{} | ||||||
|  | 	db := global.DB.Table(job.TableName()) | ||||||
|  | 	dbCounter := global.DB.Table(job.TableName()) | ||||||
|  |  | ||||||
|  | 	if len(name) > 0 { | ||||||
|  | 		db = db.Where("name like ?", "%"+name+"%") | ||||||
|  | 		dbCounter = dbCounter.Where("name like ?", "%"+name+"%") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(status) > 0 { | ||||||
|  | 		db = db.Where("status = ?", status) | ||||||
|  | 		dbCounter = dbCounter.Where("status = ?", status) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = dbCounter.Count(&total).Error | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, 0, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = db.Order("created desc").Find(&o).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Error | ||||||
|  | 	if o == nil { | ||||||
|  | 		o = make([]Job, 0) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func FindJobByFunc(function string) (o []Job, err error) { | ||||||
|  | 	db := global.DB | ||||||
|  | 	err = db.Where("func = ?", function).Find(&o).Error | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func CreateNewJob(o *Job) (err error) { | ||||||
|  |  | ||||||
|  | 	if o.Status == JobStatusRunning { | ||||||
|  | 		j, err := getJob(o.ID, o.Func) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		jobId, err := global.Cron.AddJob(o.Cron, j) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		o.JobId = int(jobId) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return global.DB.Create(o).Error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func UpdateJobById(o *Job, id string) (err error) { | ||||||
|  | 	if o.Status == JobStatusRunning { | ||||||
|  | 		return errors.New("请先停止定时任务后再修改") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return global.DB.Where("id = ?", id).Updates(o).Error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func UpdateJonUpdatedById(id string) (err error) { | ||||||
|  | 	err = global.DB.Where("id = ?", id).Update("updated = ?", utils.NowJsonTime()).Error | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ChangeJobStatusById(id, status string) (err error) { | ||||||
|  | 	var job Job | ||||||
|  | 	err = global.DB.Where("id = ?", id).First(&job).Error | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if status == JobStatusNotRunning { | ||||||
|  | 		j, err := getJob(job.ID, job.Func) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		entryID, err := global.Cron.AddJob(job.Cron, j) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		job.JobId = int(entryID) | ||||||
|  | 		return global.DB.Where("id = ?", id).Update("status = ?", JobStatusRunning).Error | ||||||
|  | 	} else { | ||||||
|  | 		global.Cron.Remove(cron.EntryID(job.JobId)) | ||||||
|  | 		return global.DB.Where("id = ?", id).Update("status = ?", JobStatusNotRunning).Error | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func FindJobById(id string) (o Job, err error) { | ||||||
|  | 	err = global.DB.Where("id = ?").First(&o).Error | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func DeleteJobById(id string) error { | ||||||
|  | 	job, err := FindJobById(id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if job.Status == JobStatusRunning { | ||||||
|  | 		if err := ChangeJobStatusById(JobStatusNotRunning, id); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return global.DB.Where("id = ?").Delete(Job{}).Error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getJob(id, function string) (job cron.Job, err error) { | ||||||
|  | 	switch function { | ||||||
|  | 	case FuncCheckAssetStatusJob: | ||||||
|  | 		job = CheckAssetStatusJob{ID: id} | ||||||
|  | 	case FuncDelUnUsedSessionJob: | ||||||
|  | 		job = DelUnUsedSessionJob{ID: id} | ||||||
|  | 	case FuncDelTimeoutSessionJob: | ||||||
|  | 		job = DelTimeoutSessionJob{ID: id} | ||||||
|  | 	default: | ||||||
|  | 		return nil, errors.New("未识别的任务") | ||||||
|  | 	} | ||||||
|  | 	return job, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type CheckAssetStatusJob struct { | ||||||
|  | 	ID string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r CheckAssetStatusJob) Run() { | ||||||
|  | 	assets, _ := FindAllAsset() | ||||||
|  | 	if assets != nil && len(assets) > 0 { | ||||||
|  | 		for i := range assets { | ||||||
|  | 			asset := assets[i] | ||||||
|  | 			active := utils.Tcping(asset.IP, asset.Port) | ||||||
|  | 			UpdateAssetActiveById(active, asset.ID) | ||||||
|  | 			logrus.Infof("资产「%v」ID「%v」存活状态检测完成,存活「%v」。", asset.Name, asset.ID, active) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if r.ID != "" { | ||||||
|  | 		_ = UpdateJonUpdatedById(r.ID) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type DelUnUsedSessionJob struct { | ||||||
|  | 	ID string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r DelUnUsedSessionJob) Run() { | ||||||
|  | 	sessions, _ := FindSessionByStatusIn([]string{NoConnect, Connecting}) | ||||||
|  | 	if sessions != nil && len(sessions) > 0 { | ||||||
|  | 		now := time.Now() | ||||||
|  | 		for i := range sessions { | ||||||
|  | 			if now.Sub(sessions[i].ConnectedTime.Time) > time.Hour*1 { | ||||||
|  | 				_ = DeleteSessionById(sessions[i].ID) | ||||||
|  | 				s := sessions[i].Username + "@" + sessions[i].IP + ":" + strconv.Itoa(sessions[i].Port) | ||||||
|  | 				logrus.Infof("会话「%v」ID「%v」超过1小时未打开,已删除。", s, sessions[i].ID) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if r.ID != "" { | ||||||
|  | 		_ = UpdateJonUpdatedById(r.ID) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type DelTimeoutSessionJob struct { | ||||||
|  | 	ID string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r DelTimeoutSessionJob) Run() { | ||||||
|  | 	property, err := FindPropertyByName("session-saved-limit") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if property.Value == "" || property.Value == "-" { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	limit, err := strconv.Atoi(property.Value) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	sessions, err := FindOutTimeSessions(limit) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if sessions != nil && len(sessions) > 0 { | ||||||
|  | 		var sessionIds []string | ||||||
|  | 		for i := range sessions { | ||||||
|  | 			sessionIds = append(sessionIds, sessions[i].ID) | ||||||
|  | 		} | ||||||
|  | 		err := DeleteSessionByIds(sessionIds) | ||||||
|  | 		if err != nil { | ||||||
|  | 			logrus.Errorf("删除离线会话失败 %v", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if r.ID != "" { | ||||||
|  | 		_ = UpdateJonUpdatedById(r.ID) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -21,6 +21,7 @@ type User struct { | |||||||
| 	Enabled    bool           `json:"enabled"` | 	Enabled    bool           `json:"enabled"` | ||||||
| 	Created    utils.JsonTime `json:"created"` | 	Created    utils.JsonTime `json:"created"` | ||||||
| 	Type       string         `json:"type"` | 	Type       string         `json:"type"` | ||||||
|  | 	Mail       string         `json:"mail"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type UserVo struct { | type UserVo struct { | ||||||
|  | |||||||
| @ -16,10 +16,10 @@ import { | |||||||
|     AuditOutlined, |     AuditOutlined, | ||||||
|     BlockOutlined, |     BlockOutlined, | ||||||
|     CloudServerOutlined, |     CloudServerOutlined, | ||||||
|     CodeOutlined, |     CodeOutlined, ControlOutlined, | ||||||
|     DashboardOutlined, |     DashboardOutlined, | ||||||
|     DesktopOutlined, |     DesktopOutlined, | ||||||
|     DisconnectOutlined, |     DisconnectOutlined, GoldOutlined, | ||||||
|     IdcardOutlined, |     IdcardOutlined, | ||||||
|     LinkOutlined, |     LinkOutlined, | ||||||
|     LoginOutlined, |     LoginOutlined, | ||||||
| @ -39,6 +39,7 @@ import {isAdmin} from "./service/permission"; | |||||||
| import UserGroup from "./components/user/UserGroup"; | import UserGroup from "./components/user/UserGroup"; | ||||||
| import LoginLog from "./components/session/LoginLog"; | import LoginLog from "./components/session/LoginLog"; | ||||||
| import Term from "./components/access/Term"; | import Term from "./components/access/Term"; | ||||||
|  | import Job from "./components/job/Job"; | ||||||
|  |  | ||||||
| const {Footer, Sider} = Layout; | const {Footer, Sider} = Layout; | ||||||
|  |  | ||||||
| @ -161,23 +162,10 @@ class App extends Component { | |||||||
|                                     </Menu.Item> |                                     </Menu.Item> | ||||||
|                                 </SubMenu> |                                 </SubMenu> | ||||||
|  |  | ||||||
|                                 <SubMenu key='command-manage' title='指令管理' icon={<CodeOutlined/>}> |  | ||||||
|                                     <Menu.Item key="dynamic-command" icon={<BlockOutlined/>}> |  | ||||||
|                                         <Link to={'/dynamic-command'}> |  | ||||||
|                                             动态指令 |  | ||||||
|                                         </Link> |  | ||||||
|                                     </Menu.Item> |  | ||||||
|                                     {/*<Menu.Item key="silent-command" icon={<DeploymentUnitOutlined/>}> |  | ||||||
|                                                 <Link to={'/silent-command'}> |  | ||||||
|                                                     静默指令 |  | ||||||
|                                                 </Link> |  | ||||||
|                                             </Menu.Item>*/} |  | ||||||
|                                 </SubMenu> |  | ||||||
|  |  | ||||||
|                                 { |                                 { | ||||||
|                                     this.state.triggerMenu && isAdmin() ? |                                     this.state.triggerMenu && isAdmin() ? | ||||||
|                                         <> |                                         <> | ||||||
|                                             <SubMenu key='audit' title='操作审计' icon={<AuditOutlined/>}> |                                             <SubMenu key='audit' title='会话审计' icon={<AuditOutlined/>}> | ||||||
|                                                 <Menu.Item key="online-session" icon={<LinkOutlined/>}> |                                                 <Menu.Item key="online-session" icon={<LinkOutlined/>}> | ||||||
|                                                     <Link to={'/online-session'}> |                                                     <Link to={'/online-session'}> | ||||||
|                                                         在线会话 |                                                         在线会话 | ||||||
| @ -195,6 +183,28 @@ class App extends Component { | |||||||
|                                                         登录日志 |                                                         登录日志 | ||||||
|                                                     </Link> |                                                     </Link> | ||||||
|                                                 </Menu.Item> |                                                 </Menu.Item> | ||||||
|  |  | ||||||
|  |                                             </SubMenu> | ||||||
|  |  | ||||||
|  |                                             <SubMenu key='ops' title='系统运维' icon={<ControlOutlined />}> | ||||||
|  |  | ||||||
|  |                                                 <Menu.Item key="dynamic-command" icon={<CodeOutlined/>}> | ||||||
|  |                                                     <Link to={'/dynamic-command'}> | ||||||
|  |                                                         动态指令 | ||||||
|  |                                                     </Link> | ||||||
|  |                                                 </Menu.Item> | ||||||
|  |  | ||||||
|  |                                                 {/*<Menu.Item key="silent-command" icon={<DeploymentUnitOutlined/>}> | ||||||
|  |                                                 <Link to={'/silent-command'}> | ||||||
|  |                                                     静默指令 | ||||||
|  |                                                 </Link> | ||||||
|  |                                             </Menu.Item>*/} | ||||||
|  |  | ||||||
|  |                                                 <Menu.Item key="job" icon={<BlockOutlined />}> | ||||||
|  |                                                     <Link to={'/job'}> | ||||||
|  |                                                         定时任务 | ||||||
|  |                                                     </Link> | ||||||
|  |                                                 </Menu.Item> | ||||||
|                                             </SubMenu> |                                             </SubMenu> | ||||||
|  |  | ||||||
|                                             <SubMenu key='user-group' title='用户管理' icon={<UserSwitchOutlined/>}> |                                             <SubMenu key='user-group' title='用户管理' icon={<UserSwitchOutlined/>}> | ||||||
| @ -251,6 +261,7 @@ class App extends Component { | |||||||
|                             <Route path="/login-log" component={LoginLog}/> |                             <Route path="/login-log" component={LoginLog}/> | ||||||
|                             <Route path="/info" component={Info}/> |                             <Route path="/info" component={Info}/> | ||||||
|                             <Route path="/setting" component={Setting}/> |                             <Route path="/setting" component={Setting}/> | ||||||
|  |                             <Route path="/job" component={Job}/> | ||||||
|  |  | ||||||
|                             <Footer style={{textAlign: 'center'}}> |                             <Footer style={{textAlign: 'center'}}> | ||||||
|                                 Next Terminal ©2021 dushixiang Version:{this.state.package['version']} |                                 Next Terminal ©2021 dushixiang Version:{this.state.package['version']} | ||||||
|  | |||||||
							
								
								
									
										450
									
								
								web/src/components/job/Job.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										450
									
								
								web/src/components/job/Job.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,450 @@ | |||||||
|  | import React, {Component} from 'react'; | ||||||
|  |  | ||||||
|  | import { | ||||||
|  |     Button, | ||||||
|  |     Checkbox, | ||||||
|  |     Col, | ||||||
|  |     Divider, | ||||||
|  |     Dropdown, | ||||||
|  |     Input, | ||||||
|  |     Layout, | ||||||
|  |     Menu, | ||||||
|  |     Modal, | ||||||
|  |     PageHeader, | ||||||
|  |     Row, | ||||||
|  |     Space, | ||||||
|  |     Table, | ||||||
|  |     Tooltip, | ||||||
|  |     Typography | ||||||
|  | } from "antd"; | ||||||
|  | import qs from "qs"; | ||||||
|  | import request from "../../common/request"; | ||||||
|  | import {message} from "antd/es"; | ||||||
|  | import { | ||||||
|  |     DeleteOutlined, | ||||||
|  |     DownOutlined, | ||||||
|  |     ExclamationCircleOutlined, | ||||||
|  |     PlusOutlined, | ||||||
|  |     SyncOutlined, | ||||||
|  |     UndoOutlined | ||||||
|  | } from '@ant-design/icons'; | ||||||
|  | import {itemRender} from "../../utils/utils"; | ||||||
|  | import Logout from "../user/Logout"; | ||||||
|  | import {hasPermission} from "../../service/permission"; | ||||||
|  | import dayjs from "dayjs"; | ||||||
|  |  | ||||||
|  | const confirm = Modal.confirm; | ||||||
|  | const {Content} = Layout; | ||||||
|  | const {Title, Text} = Typography; | ||||||
|  | const {Search} = Input; | ||||||
|  | const CheckboxGroup = Checkbox.Group; | ||||||
|  | const routes = [ | ||||||
|  |     { | ||||||
|  |         path: '', | ||||||
|  |         breadcrumbName: '首页', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |         path: 'job', | ||||||
|  |         breadcrumbName: '定时任务', | ||||||
|  |     } | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | class Job extends Component { | ||||||
|  |  | ||||||
|  |     inputRefOfName = React.createRef(); | ||||||
|  |  | ||||||
|  |     state = { | ||||||
|  |         items: [], | ||||||
|  |         total: 0, | ||||||
|  |         queryParams: { | ||||||
|  |             pageIndex: 1, | ||||||
|  |             pageSize: 10 | ||||||
|  |         }, | ||||||
|  |         loading: false, | ||||||
|  |         modalVisible: false, | ||||||
|  |         modalTitle: '', | ||||||
|  |         modalConfirmLoading: false, | ||||||
|  |         selectedRowKeys: [] | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     componentDidMount() { | ||||||
|  |         this.loadTableData(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async delete(id) { | ||||||
|  |         const result = await request.delete('/jobs/' + id); | ||||||
|  |         if (result.code === 1) { | ||||||
|  |             message.success('删除成功'); | ||||||
|  |             this.loadTableData(this.state.queryParams); | ||||||
|  |         } else { | ||||||
|  |             message.error('删除失败 :( ' + result.message, 10); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async loadTableData(queryParams) { | ||||||
|  |         this.setState({ | ||||||
|  |             loading: true | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         queryParams = queryParams || this.state.queryParams; | ||||||
|  |  | ||||||
|  |         // queryParams | ||||||
|  |         let paramsStr = qs.stringify(queryParams); | ||||||
|  |  | ||||||
|  |         let data = { | ||||||
|  |             items: [], | ||||||
|  |             total: 0 | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             let result = await request.get('/jobs/paging?' + paramsStr); | ||||||
|  |             if (result.code === 1) { | ||||||
|  |                 data = result.data; | ||||||
|  |             } else { | ||||||
|  |                 message.error(result.message); | ||||||
|  |             } | ||||||
|  |         } catch (e) { | ||||||
|  |  | ||||||
|  |         } finally { | ||||||
|  |             const items = data.items.map(item => { | ||||||
|  |                 return {'key': item['id'], ...item} | ||||||
|  |             }) | ||||||
|  |             this.setState({ | ||||||
|  |                 items: items, | ||||||
|  |                 total: data.total, | ||||||
|  |                 queryParams: queryParams, | ||||||
|  |                 loading: false | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     handleChangPage = async (pageIndex, pageSize) => { | ||||||
|  |         let queryParams = this.state.queryParams; | ||||||
|  |         queryParams.pageIndex = pageIndex; | ||||||
|  |         queryParams.pageSize = pageSize; | ||||||
|  |  | ||||||
|  |         this.setState({ | ||||||
|  |             queryParams: queryParams | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         await this.loadTableData(queryParams) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     handleSearchByName = name => { | ||||||
|  |         let query = { | ||||||
|  |             ...this.state.queryParams, | ||||||
|  |             'pageIndex': 1, | ||||||
|  |             'pageSize': this.state.queryParams.pageSize, | ||||||
|  |             'name': name, | ||||||
|  |         } | ||||||
|  |         this.loadTableData(query); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     showDeleteConfirm(id, content) { | ||||||
|  |         let self = this; | ||||||
|  |         confirm({ | ||||||
|  |             title: '您确定要删除此任务吗?', | ||||||
|  |             content: content, | ||||||
|  |             okText: '确定', | ||||||
|  |             okType: 'danger', | ||||||
|  |             cancelText: '取消', | ||||||
|  |             onOk() { | ||||||
|  |                 self.delete(id); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     showModal(title, assets = null) { | ||||||
|  |         this.setState({ | ||||||
|  |             modalTitle: title, | ||||||
|  |             modalVisible: true, | ||||||
|  |             model: assets | ||||||
|  |         }); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     handleCancelModal = e => { | ||||||
|  |         this.setState({ | ||||||
|  |             modalTitle: '', | ||||||
|  |             modalVisible: false | ||||||
|  |         }); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     handleOk = async (formData) => { | ||||||
|  |         // 弹窗 form 传来的数据 | ||||||
|  |         this.setState({ | ||||||
|  |             modalConfirmLoading: true | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         if (formData.id) { | ||||||
|  |             // 向后台提交数据 | ||||||
|  |             const result = await request.put('/jobs/' + formData.id, formData); | ||||||
|  |             if (result.code === 1) { | ||||||
|  |                 message.success('更新成功'); | ||||||
|  |  | ||||||
|  |                 this.setState({ | ||||||
|  |                     modalVisible: false | ||||||
|  |                 }); | ||||||
|  |                 this.loadTableData(this.state.queryParams); | ||||||
|  |             } else { | ||||||
|  |                 message.error('更新失败 :( ' + result.message, 10); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             // 向后台提交数据 | ||||||
|  |             const result = await request.post('/jobs', formData); | ||||||
|  |             if (result.code === 1) { | ||||||
|  |                 message.success('新增成功'); | ||||||
|  |  | ||||||
|  |                 this.setState({ | ||||||
|  |                     modalVisible: false | ||||||
|  |                 }); | ||||||
|  |                 this.loadTableData(this.state.queryParams); | ||||||
|  |             } else { | ||||||
|  |                 message.error('新增失败 :( ' + result.message, 10); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         this.setState({ | ||||||
|  |             modalConfirmLoading: false | ||||||
|  |         }); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     batchDelete = async () => { | ||||||
|  |         this.setState({ | ||||||
|  |             delBtnLoading: true | ||||||
|  |         }) | ||||||
|  |         try { | ||||||
|  |             let result = await request.delete('/jobs/' + this.state.selectedRowKeys.join(',')); | ||||||
|  |             if (result.code === 1) { | ||||||
|  |                 message.success('操作成功', 3); | ||||||
|  |                 this.setState({ | ||||||
|  |                     selectedRowKeys: [] | ||||||
|  |                 }) | ||||||
|  |                 await this.loadTableData(this.state.queryParams); | ||||||
|  |             } else { | ||||||
|  |                 message.error('删除失败 :( ' + result.message, 10); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             this.setState({ | ||||||
|  |                 delBtnLoading: false | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     render() { | ||||||
|  |  | ||||||
|  |         const columns = [{ | ||||||
|  |             title: '序号', | ||||||
|  |             dataIndex: 'id', | ||||||
|  |             key: 'id', | ||||||
|  |             render: (id, record, index) => { | ||||||
|  |                 return index + 1; | ||||||
|  |             } | ||||||
|  |         }, { | ||||||
|  |             title: '任务名称', | ||||||
|  |             dataIndex: 'name', | ||||||
|  |             key: 'name', | ||||||
|  |             render: (name, record) => { | ||||||
|  |                 let short = name; | ||||||
|  |                 if (short && short.length > 20) { | ||||||
|  |                     short = short.substring(0, 20) + " ..."; | ||||||
|  |                 } | ||||||
|  |                 return ( | ||||||
|  |                     <Tooltip placement="topLeft" title={name}> | ||||||
|  |                         {short} | ||||||
|  |                     </Tooltip> | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |         }, { | ||||||
|  |             title: '任务类型', | ||||||
|  |             dataIndex: 'func', | ||||||
|  |             key: 'func' | ||||||
|  |         }, { | ||||||
|  |             title: 'cron表达式', | ||||||
|  |             dataIndex: 'cron', | ||||||
|  |             key: 'cron' | ||||||
|  |         }, { | ||||||
|  |             title: '创建日期', | ||||||
|  |             dataIndex: 'created', | ||||||
|  |             key: 'created', | ||||||
|  |             render: (text, record) => { | ||||||
|  |                 return ( | ||||||
|  |                     <Tooltip title={text}> | ||||||
|  |                         {dayjs(text).fromNow()} | ||||||
|  |                     </Tooltip> | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         }, { | ||||||
|  |             title: '更新日期', | ||||||
|  |             dataIndex: 'updated', | ||||||
|  |             key: 'updated', | ||||||
|  |             render: (text, record) => { | ||||||
|  |                 return ( | ||||||
|  |                     <Tooltip title={text}> | ||||||
|  |                         {dayjs(text).fromNow()} | ||||||
|  |                     </Tooltip> | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         }, { | ||||||
|  |             title: '操作', | ||||||
|  |             key: 'action', | ||||||
|  |             render: (text, record) => { | ||||||
|  |  | ||||||
|  |                 const menu = ( | ||||||
|  |                     <Menu> | ||||||
|  |                         <Menu.Item key="0"> | ||||||
|  |                             <Button type="text" size='small' | ||||||
|  |                                     disabled={!hasPermission(record['owner'])} | ||||||
|  |                                     onClick={() => this.showModal('更新指令', record)}>编辑</Button> | ||||||
|  |                         </Menu.Item> | ||||||
|  |  | ||||||
|  |                         <Menu.Divider/> | ||||||
|  |                         <Menu.Item key="3"> | ||||||
|  |                             <Button type="text" size='small' danger | ||||||
|  |                                     disabled={!hasPermission(record['owner'])} | ||||||
|  |                                     onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button> | ||||||
|  |                         </Menu.Item> | ||||||
|  |                     </Menu> | ||||||
|  |                 ); | ||||||
|  |  | ||||||
|  |                 return ( | ||||||
|  |                     <div> | ||||||
|  |                         <Button type="link" size='small' onClick={async () => { | ||||||
|  |  | ||||||
|  |                         }}>执行</Button> | ||||||
|  |  | ||||||
|  |                         <Dropdown overlay={menu}> | ||||||
|  |                             <Button type="link" size='small'> | ||||||
|  |                                 更多 <DownOutlined/> | ||||||
|  |                             </Button> | ||||||
|  |                         </Dropdown> | ||||||
|  |  | ||||||
|  |                     </div> | ||||||
|  |                 ) | ||||||
|  |             }, | ||||||
|  |         } | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         const selectedRowKeys = this.state.selectedRowKeys; | ||||||
|  |         const rowSelection = { | ||||||
|  |             selectedRowKeys: this.state.selectedRowKeys, | ||||||
|  |             onChange: (selectedRowKeys, selectedRows) => { | ||||||
|  |                 this.setState({selectedRowKeys}); | ||||||
|  |             }, | ||||||
|  |         }; | ||||||
|  |         const hasSelected = selectedRowKeys.length > 0; | ||||||
|  |  | ||||||
|  |         return ( | ||||||
|  |             <> | ||||||
|  |                 <PageHeader | ||||||
|  |                     className="site-page-header-ghost-wrapper page-herder" | ||||||
|  |                     title="定时任务" | ||||||
|  |                     breadcrumb={{ | ||||||
|  |                         routes: routes, | ||||||
|  |                         itemRender: itemRender | ||||||
|  |                     }} | ||||||
|  |                     extra={[ | ||||||
|  |                         <Logout key='logout'/> | ||||||
|  |                     ]} | ||||||
|  |                     subTitle="定时任务" | ||||||
|  |                 > | ||||||
|  |                 </PageHeader> | ||||||
|  |  | ||||||
|  |                 <Content className="site-layout-background page-content"> | ||||||
|  |  | ||||||
|  |                     <div style={{marginBottom: 20}}> | ||||||
|  |                         <Row justify="space-around" align="middle" gutter={24}> | ||||||
|  |                             <Col span={12} key={1}> | ||||||
|  |                                 <Title level={3}>任务列表</Title> | ||||||
|  |                             </Col> | ||||||
|  |                             <Col span={12} key={2} style={{textAlign: 'right'}}> | ||||||
|  |                                 <Space> | ||||||
|  |                                     <Search | ||||||
|  |                                         ref={this.inputRefOfName} | ||||||
|  |                                         placeholder="任务名称" | ||||||
|  |                                         allowClear | ||||||
|  |                                         onSearch={this.handleSearchByName} | ||||||
|  |                                     /> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                                     <Tooltip title='重置查询'> | ||||||
|  |  | ||||||
|  |                                         <Button icon={<UndoOutlined/>} onClick={() => { | ||||||
|  |                                             this.inputRefOfName.current.setValue(''); | ||||||
|  |                                             this.loadTableData({pageIndex: 1, pageSize: 10, name: '', content: ''}) | ||||||
|  |                                         }}> | ||||||
|  |  | ||||||
|  |                                         </Button> | ||||||
|  |                                     </Tooltip> | ||||||
|  |  | ||||||
|  |                                     <Divider type="vertical"/> | ||||||
|  |  | ||||||
|  |                                     <Tooltip title="新增"> | ||||||
|  |                                         <Button type="dashed" icon={<PlusOutlined/>} | ||||||
|  |                                                 onClick={() => this.showModal('新增任务', {})}> | ||||||
|  |  | ||||||
|  |                                         </Button> | ||||||
|  |                                     </Tooltip> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                                     <Tooltip title="刷新列表"> | ||||||
|  |                                         <Button icon={<SyncOutlined/>} onClick={() => { | ||||||
|  |                                             this.loadTableData(this.state.queryParams) | ||||||
|  |                                         }}> | ||||||
|  |  | ||||||
|  |                                         </Button> | ||||||
|  |                                     </Tooltip> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                                     <Tooltip title="批量删除"> | ||||||
|  |                                         <Button type="primary" danger disabled={!hasSelected} icon={<DeleteOutlined/>} | ||||||
|  |                                                 loading={this.state.delBtnLoading} | ||||||
|  |                                                 onClick={() => { | ||||||
|  |                                                     const content = <div> | ||||||
|  |                                                         您确定要删除选中的<Text style={{color: '#1890FF'}} | ||||||
|  |                                                                        strong>{this.state.selectedRowKeys.length}</Text>条记录吗? | ||||||
|  |                                                     </div>; | ||||||
|  |                                                     confirm({ | ||||||
|  |                                                         icon: <ExclamationCircleOutlined/>, | ||||||
|  |                                                         content: content, | ||||||
|  |                                                         onOk: () => { | ||||||
|  |                                                             this.batchDelete() | ||||||
|  |                                                         }, | ||||||
|  |                                                         onCancel() { | ||||||
|  |  | ||||||
|  |                                                         }, | ||||||
|  |                                                     }); | ||||||
|  |                                                 }}> | ||||||
|  |  | ||||||
|  |                                         </Button> | ||||||
|  |                                     </Tooltip> | ||||||
|  |  | ||||||
|  |                                 </Space> | ||||||
|  |                             </Col> | ||||||
|  |                         </Row> | ||||||
|  |                     </div> | ||||||
|  |  | ||||||
|  |                     <Table | ||||||
|  |                         rowSelection={rowSelection} | ||||||
|  |                         dataSource={this.state.items} | ||||||
|  |                         columns={columns} | ||||||
|  |                         position={'both'} | ||||||
|  |                         pagination={{ | ||||||
|  |                             showSizeChanger: true, | ||||||
|  |                             current: this.state.queryParams.pageIndex, | ||||||
|  |                             pageSize: this.state.queryParams.pageSize, | ||||||
|  |                             onChange: this.handleChangPage, | ||||||
|  |                             onShowSizeChange: this.handleChangPage, | ||||||
|  |                             total: this.state.total, | ||||||
|  |                             showTotal: total => `总计 ${total} 条` | ||||||
|  |                         }} | ||||||
|  |                         loading={this.state.loading} | ||||||
|  |                     /> | ||||||
|  |  | ||||||
|  |                 </Content> | ||||||
|  |             </> | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default Job; | ||||||
		Reference in New Issue
	
	Block a user