完善计划任务日志功能
This commit is contained in:
parent
1b8ecefcfe
commit
2d06cd373f
21
main.go
21
main.go
@ -159,6 +159,9 @@ func Run() error {
|
|||||||
if err := global.DB.AutoMigrate(&model.Job{}); err != nil {
|
if err := global.DB.AutoMigrate(&model.Job{}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := global.DB.AutoMigrate(&model.JobLog{}); 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++ {
|
||||||
@ -197,24 +200,14 @@ func Run() error {
|
|||||||
if err := model.CreateNewJob(&job); err != nil {
|
if err := model.CreateNewJob(&job); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
for i := range jobs {
|
||||||
jobs, err = model.FindJobByFunc(model.FuncDelTimeoutSessionJob)
|
if jobs[i].Status == model.JobStatusRunning {
|
||||||
|
err := model.ChangeJobStatusById(jobs[i].ID, model.JobStatusRunning)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +60,14 @@ func JobChangeStatusEndpoint(c echo.Context) error {
|
|||||||
return Success(c, "")
|
return Success(c, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func JobExecEndpoint(c echo.Context) error {
|
||||||
|
id := c.Param("id")
|
||||||
|
if err := model.ExecJobById(id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Success(c, "")
|
||||||
|
}
|
||||||
|
|
||||||
func JobDeleteEndpoint(c echo.Context) error {
|
func JobDeleteEndpoint(c echo.Context) error {
|
||||||
ids := c.Param("id")
|
ids := c.Param("id")
|
||||||
|
|
||||||
@ -84,3 +92,22 @@ func JobGetEndpoint(c echo.Context) error {
|
|||||||
|
|
||||||
return Success(c, item)
|
return Success(c, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func JobGetLogsEndpoint(c echo.Context) error {
|
||||||
|
id := c.Param("id")
|
||||||
|
|
||||||
|
items, err := model.FindJobLogs(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Success(c, items)
|
||||||
|
}
|
||||||
|
|
||||||
|
func JobDeleteLogsEndpoint(c echo.Context) error {
|
||||||
|
id := c.Param("id")
|
||||||
|
if err := model.DeleteJobLogByJobId(id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Success(c, "")
|
||||||
|
}
|
||||||
|
@ -150,8 +150,11 @@ func SetupRoutes() *echo.Echo {
|
|||||||
jobs.GET("/paging", JobPagingEndpoint)
|
jobs.GET("/paging", JobPagingEndpoint)
|
||||||
jobs.PUT("/:id", JobUpdateEndpoint)
|
jobs.PUT("/:id", JobUpdateEndpoint)
|
||||||
jobs.POST("/:id/change-status", JobChangeStatusEndpoint)
|
jobs.POST("/:id/change-status", JobChangeStatusEndpoint)
|
||||||
|
jobs.POST("/:id/exec", JobExecEndpoint)
|
||||||
jobs.DELETE("/:id", JobDeleteEndpoint)
|
jobs.DELETE("/:id", JobDeleteEndpoint)
|
||||||
jobs.GET("/:id", JobGetEndpoint)
|
jobs.GET("/:id", JobGetEndpoint)
|
||||||
|
jobs.GET("/:id/logs", JobGetLogsEndpoint)
|
||||||
|
jobs.DELETE("/:id/logs", JobDeleteLogsEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
return e
|
return e
|
||||||
|
@ -1,21 +1,61 @@
|
|||||||
package handle
|
package handle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"next-terminal/pkg/global"
|
"next-terminal/pkg/global"
|
||||||
"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() {
|
||||||
|
|
||||||
// 每隔一小时删除一次未使用的会话信息
|
// 每隔一小时删除一次未使用的会话信息
|
||||||
_, _ = global.Cron.AddJob("0 0 0/1 * * ?", model.DelUnUsedSessionJob{})
|
_, _ = global.Cron.AddFunc("0 0 0/1 * * ?", func() {
|
||||||
// 每隔一小时检测一次资产状态
|
sessions, _ := model.FindSessionByStatusIn([]string{model.NoConnect, model.Connecting})
|
||||||
//_, _ = global.Cron.AddJob("0 0 0/1 * * ?", model.CheckAssetStatusJob{})
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
// 每日凌晨删除超过时长限制的会话
|
// 每日凌晨删除超过时长限制的会话
|
||||||
//_, _ = global.Cron.AddJob("0 0 0 * * ?", model.DelTimeoutSessionJob{})
|
_, _ = global.Cron.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
global.Cron.Start()
|
global.Cron.Start()
|
||||||
}
|
}
|
||||||
|
125
pkg/model/job.go
125
pkg/model/job.go
@ -2,11 +2,11 @@ package model
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"next-terminal/pkg/global"
|
"next-terminal/pkg/global"
|
||||||
"next-terminal/pkg/utils"
|
"next-terminal/pkg/utils"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,17 +15,16 @@ const (
|
|||||||
JobStatusNotRunning = "not-running"
|
JobStatusNotRunning = "not-running"
|
||||||
|
|
||||||
FuncCheckAssetStatusJob = "check-asset-status-job"
|
FuncCheckAssetStatusJob = "check-asset-status-job"
|
||||||
FuncDelUnUsedSessionJob = "del-unused-session-job"
|
|
||||||
FuncDelTimeoutSessionJob = "del-timeout-session-job"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Job struct {
|
type Job struct {
|
||||||
ID string `gorm:"primary_key" json:"id"`
|
ID string `gorm:"primary_key" json:"id"`
|
||||||
JobId int `json:"jobId"`
|
CronJobId int `json:"cronJobId"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Func string `json:"func"`
|
Func string `json:"func"`
|
||||||
Cron string `json:"cron"`
|
Cron string `json:"cron"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
Metadata string `json:"metadata"`
|
||||||
Created utils.JsonTime `json:"created"`
|
Created utils.JsonTime `json:"created"`
|
||||||
Updated utils.JsonTime `json:"updated"`
|
Updated utils.JsonTime `json:"updated"`
|
||||||
}
|
}
|
||||||
@ -78,7 +77,7 @@ func CreateNewJob(o *Job) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
o.JobId = int(jobId)
|
o.CronJobId = int(jobId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return global.DB.Create(o).Error
|
return global.DB.Create(o).Error
|
||||||
@ -93,7 +92,7 @@ func UpdateJobById(o *Job, id string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UpdateJonUpdatedById(id string) (err error) {
|
func UpdateJonUpdatedById(id string) (err error) {
|
||||||
err = global.DB.Where("id = ?", id).Update("updated = ?", utils.NowJsonTime()).Error
|
err = global.DB.Updates(Job{ID: id, Updated: utils.NowJsonTime()}).Error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +102,7 @@ func ChangeJobStatusById(id, status string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if status == JobStatusNotRunning {
|
if status == JobStatusRunning {
|
||||||
j, err := getJob(job.ID, job.Func)
|
j, err := getJob(job.ID, job.Func)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -112,16 +111,29 @@ func ChangeJobStatusById(id, status string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
job.JobId = int(entryID)
|
job.CronJobId = int(entryID)
|
||||||
return global.DB.Where("id = ?", id).Update("status = ?", JobStatusRunning).Error
|
return global.DB.Updates(Job{ID: id, Status: JobStatusRunning}).Error
|
||||||
} else {
|
} else {
|
||||||
global.Cron.Remove(cron.EntryID(job.JobId))
|
global.Cron.Remove(cron.EntryID(job.CronJobId))
|
||||||
return global.DB.Where("id = ?", id).Update("status = ?", JobStatusNotRunning).Error
|
return global.DB.Updates(Job{ID: id, Status: JobStatusNotRunning}).Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExecJobById(id string) (err error) {
|
||||||
|
job, err := FindJobById(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
j, err := getJob(id, job.Func)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
j.Run()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func FindJobById(id string) (o Job, err error) {
|
func FindJobById(id string) (o Job, err error) {
|
||||||
err = global.DB.Where("id = ?").First(&o).Error
|
err = global.DB.Where("id = ?", id).First(&o).Error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,21 +143,17 @@ func DeleteJobById(id string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if job.Status == JobStatusRunning {
|
if job.Status == JobStatusRunning {
|
||||||
if err := ChangeJobStatusById(JobStatusNotRunning, id); err != nil {
|
if err := ChangeJobStatusById(id, JobStatusNotRunning); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return global.DB.Where("id = ?").Delete(Job{}).Error
|
return global.DB.Where("id = ?", id).Delete(Job{}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func getJob(id, function string) (job cron.Job, err error) {
|
func getJob(id, function string) (job cron.Job, err error) {
|
||||||
switch function {
|
switch function {
|
||||||
case FuncCheckAssetStatusJob:
|
case FuncCheckAssetStatusJob:
|
||||||
job = CheckAssetStatusJob{ID: id}
|
job = CheckAssetStatusJob{ID: id}
|
||||||
case FuncDelUnUsedSessionJob:
|
|
||||||
job = DelUnUsedSessionJob{ID: id}
|
|
||||||
case FuncDelTimeoutSessionJob:
|
|
||||||
job = DelTimeoutSessionJob{ID: id}
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("未识别的任务")
|
return nil, errors.New("未识别的任务")
|
||||||
}
|
}
|
||||||
@ -159,71 +167,38 @@ type CheckAssetStatusJob struct {
|
|||||||
func (r CheckAssetStatusJob) Run() {
|
func (r CheckAssetStatusJob) Run() {
|
||||||
assets, _ := FindAllAsset()
|
assets, _ := FindAllAsset()
|
||||||
if assets != nil && len(assets) > 0 {
|
if assets != nil && len(assets) > 0 {
|
||||||
|
|
||||||
|
msgChan := make(chan string)
|
||||||
for i := range assets {
|
for i := range assets {
|
||||||
asset := assets[i]
|
asset := assets[i]
|
||||||
|
go func() {
|
||||||
|
t1 := time.Now()
|
||||||
active := utils.Tcping(asset.IP, asset.Port)
|
active := utils.Tcping(asset.IP, asset.Port)
|
||||||
|
elapsed := time.Since(t1)
|
||||||
|
msg := fmt.Sprintf("资产「%v」存活状态检测完成,存活「%v」,耗时「%v」", asset.Name, active, elapsed)
|
||||||
|
|
||||||
UpdateAssetActiveById(active, asset.ID)
|
UpdateAssetActiveById(active, asset.ID)
|
||||||
logrus.Infof("资产「%v」ID「%v」存活状态检测完成,存活「%v」。", asset.Name, asset.ID, active)
|
logrus.Infof(msg)
|
||||||
}
|
msgChan <- msg
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.ID != "" {
|
if r.ID != "" {
|
||||||
|
var message = ""
|
||||||
|
for i := 0; i < len(assets); i++ {
|
||||||
|
message += <-msgChan + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
_ = UpdateJonUpdatedById(r.ID)
|
_ = UpdateJonUpdatedById(r.ID)
|
||||||
|
jobLog := JobLog{
|
||||||
|
ID: utils.UUID(),
|
||||||
|
JobId: r.ID,
|
||||||
|
Timestamp: utils.NowJsonTime(),
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = CreateNewJobLog(&jobLog)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
30
pkg/model/job_log.go
Normal file
30
pkg/model/job_log.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"next-terminal/pkg/global"
|
||||||
|
"next-terminal/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JobLog struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Timestamp utils.JsonTime `json:"timestamp"`
|
||||||
|
JobId string `json:"jobId"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *JobLog) TableName() string {
|
||||||
|
return "job_logs"
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateNewJobLog(o *JobLog) error {
|
||||||
|
return global.DB.Create(o).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindJobLogs(jobId string) (o []JobLog, err error) {
|
||||||
|
err = global.DB.Where("job_id = ?", jobId).Order("timestamp asc").Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteJobLogByJobId(jobId string) error {
|
||||||
|
return global.DB.Where("job_id = ?", jobId).Delete(JobLog{}).Error
|
||||||
|
}
|
@ -90,6 +90,6 @@
|
|||||||
border-radius: 5%;
|
border-radius: 5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor .ant-modal-body{
|
.modal-no-padding .ant-modal-body{
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
@ -162,6 +162,14 @@ 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>
|
||||||
|
</SubMenu>
|
||||||
|
|
||||||
{
|
{
|
||||||
this.state.triggerMenu && isAdmin() ?
|
this.state.triggerMenu && isAdmin() ?
|
||||||
<>
|
<>
|
||||||
@ -177,6 +185,9 @@ class App extends Component {
|
|||||||
历史会话
|
历史会话
|
||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
</SubMenu>
|
||||||
|
|
||||||
|
<SubMenu key='ops' title='系统运维' icon={<ControlOutlined />}>
|
||||||
|
|
||||||
<Menu.Item key="login-log" icon={<LoginOutlined/>}>
|
<Menu.Item key="login-log" icon={<LoginOutlined/>}>
|
||||||
<Link to={'/login-log'}>
|
<Link to={'/login-log'}>
|
||||||
@ -184,25 +195,9 @@ 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 />}>
|
<Menu.Item key="job" icon={<BlockOutlined />}>
|
||||||
<Link to={'/job'}>
|
<Link to={'/job'}>
|
||||||
定时任务
|
计划任务
|
||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</SubMenu>
|
</SubMenu>
|
||||||
|
@ -377,10 +377,10 @@ class Access extends Component {
|
|||||||
|
|
||||||
let width = window.innerWidth;
|
let width = window.innerWidth;
|
||||||
let height = window.innerHeight;
|
let height = window.innerHeight;
|
||||||
let dpi = 96;
|
let dpi = Math.floor(window.devicePixelRatio * 96);
|
||||||
if (protocol === 'ssh' || protocol === 'telnet') {
|
// if (protocol === 'ssh' || protocol === 'telnet') {
|
||||||
dpi = dpi * 2;
|
// dpi = dpi * 2;
|
||||||
}
|
// }
|
||||||
|
|
||||||
let token = getToken();
|
let token = getToken();
|
||||||
|
|
||||||
|
12
web/src/components/job/Job.css
Normal file
12
web/src/components/job/Job.css
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
.cron-log {
|
||||||
|
overflow: auto;
|
||||||
|
border: 0 none;
|
||||||
|
line-height: 23px;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
height: 500px;
|
||||||
|
background-color: rgb(51, 51, 51);
|
||||||
|
color: #f1f1f1;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
@ -2,7 +2,6 @@ import React, {Component} from 'react';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
|
||||||
Col,
|
Col,
|
||||||
Divider,
|
Divider,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
@ -13,7 +12,10 @@ import {
|
|||||||
PageHeader,
|
PageHeader,
|
||||||
Row,
|
Row,
|
||||||
Space,
|
Space,
|
||||||
|
Switch,
|
||||||
Table,
|
Table,
|
||||||
|
Tag,
|
||||||
|
Timeline,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Typography
|
Typography
|
||||||
} from "antd";
|
} from "antd";
|
||||||
@ -30,14 +32,14 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import {itemRender} from "../../utils/utils";
|
import {itemRender} from "../../utils/utils";
|
||||||
import Logout from "../user/Logout";
|
import Logout from "../user/Logout";
|
||||||
import {hasPermission} from "../../service/permission";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import JobModal from "./JobModal";
|
||||||
|
import './Job.css'
|
||||||
|
|
||||||
const confirm = Modal.confirm;
|
const confirm = Modal.confirm;
|
||||||
const {Content} = Layout;
|
const {Content} = Layout;
|
||||||
const {Title, Text} = Typography;
|
const {Title, Text} = Typography;
|
||||||
const {Search} = Input;
|
const {Search} = Input;
|
||||||
const CheckboxGroup = Checkbox.Group;
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
@ -45,7 +47,7 @@ const routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'job',
|
path: 'job',
|
||||||
breadcrumbName: '定时任务',
|
breadcrumbName: '计划任务',
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -64,7 +66,9 @@ class Job extends Component {
|
|||||||
modalVisible: false,
|
modalVisible: false,
|
||||||
modalTitle: '',
|
modalTitle: '',
|
||||||
modalConfirmLoading: false,
|
modalConfirmLoading: false,
|
||||||
selectedRowKeys: []
|
selectedRow: undefined,
|
||||||
|
selectedRowKeys: [],
|
||||||
|
logs: []
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -255,10 +259,34 @@ class Job extends Component {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
render: (status, record) => {
|
||||||
|
return <Switch checkedChildren="开启" unCheckedChildren="关闭" checked={status === 'running'}
|
||||||
|
onChange={async (checked) => {
|
||||||
|
let jobStatus = checked ? 'running' : 'not-running';
|
||||||
|
let result = await request.post(`/jobs/${record['id']}/change-status?status=${jobStatus}`);
|
||||||
|
if (result['code'] === 1) {
|
||||||
|
message.success('操作成功');
|
||||||
|
await this.loadTableData();
|
||||||
|
} else {
|
||||||
|
message.error(result['message']);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
title: '任务类型',
|
title: '任务类型',
|
||||||
dataIndex: 'func',
|
dataIndex: 'func',
|
||||||
key: 'func'
|
key: 'func',
|
||||||
|
render: (func, record) => {
|
||||||
|
switch (func) {
|
||||||
|
case "check-asset-status-job":
|
||||||
|
return <Tag color="green">资产状态检测</Tag>
|
||||||
|
}
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
title: 'cron表达式',
|
title: 'cron表达式',
|
||||||
dataIndex: 'cron',
|
dataIndex: 'cron',
|
||||||
@ -275,7 +303,7 @@ class Job extends Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
title: '更新日期',
|
title: '最后执行日期',
|
||||||
dataIndex: 'updated',
|
dataIndex: 'updated',
|
||||||
key: 'updated',
|
key: 'updated',
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
@ -288,20 +316,38 @@ class Job extends Component {
|
|||||||
}, {
|
}, {
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
render: (text, record) => {
|
render: (text, record, index) => {
|
||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
<Menu>
|
<Menu>
|
||||||
<Menu.Item key="0">
|
<Menu.Item key="0">
|
||||||
<Button type="text" size='small'
|
<Button type="text" size='small'
|
||||||
disabled={!hasPermission(record['owner'])}
|
onClick={() => this.showModal('更新计划任务', record)}>编辑</Button>
|
||||||
onClick={() => this.showModal('更新指令', record)}>编辑</Button>
|
</Menu.Item>
|
||||||
|
|
||||||
|
<Menu.Item key="2">
|
||||||
|
<Button type="text" size='small'
|
||||||
|
onClick={async () => {
|
||||||
|
this.setState({
|
||||||
|
logVisible: true,
|
||||||
|
logPending: '正在加载...'
|
||||||
|
})
|
||||||
|
|
||||||
|
let result = await request.get(`/jobs/${record['id']}/logs`);
|
||||||
|
if (result['code'] === 1) {
|
||||||
|
this.setState({
|
||||||
|
logPending: false,
|
||||||
|
logs: result['data'],
|
||||||
|
selectedRow: record
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}}>日志</Button>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
||||||
<Menu.Divider/>
|
<Menu.Divider/>
|
||||||
<Menu.Item key="3">
|
<Menu.Item key="3">
|
||||||
<Button type="text" size='small' danger
|
<Button type="text" size='small' danger
|
||||||
disabled={!hasPermission(record['owner'])}
|
|
||||||
onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button>
|
onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
@ -309,8 +355,25 @@ class Job extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button type="link" size='small' onClick={async () => {
|
<Button type="link" size='small' loading={this.state.items[index]['execLoading']}
|
||||||
|
onClick={async () => {
|
||||||
|
let items = this.state.items;
|
||||||
|
items[index]['execLoading'] = true;
|
||||||
|
this.setState({
|
||||||
|
items: items
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = await request.post(`/jobs/${record['id']}/exec`);
|
||||||
|
if (result['code'] === 1) {
|
||||||
|
message.success('执行成功');
|
||||||
|
await this.loadTableData();
|
||||||
|
} else {
|
||||||
|
message.error(result['message']);
|
||||||
|
items[index]['execLoading'] = false;
|
||||||
|
this.setState({
|
||||||
|
items: items
|
||||||
|
});
|
||||||
|
}
|
||||||
}}>执行</Button>
|
}}>执行</Button>
|
||||||
|
|
||||||
<Dropdown overlay={menu}>
|
<Dropdown overlay={menu}>
|
||||||
@ -441,6 +504,65 @@ class Job extends Component {
|
|||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
this.state.modalVisible ?
|
||||||
|
<JobModal
|
||||||
|
visible={this.state.modalVisible}
|
||||||
|
title={this.state.modalTitle}
|
||||||
|
handleOk={this.handleOk}
|
||||||
|
handleCancel={this.handleCancelModal}
|
||||||
|
confirmLoading={this.state.modalConfirmLoading}
|
||||||
|
model={this.state.model}
|
||||||
|
>
|
||||||
|
</JobModal> : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
this.state.logVisible ?
|
||||||
|
<Modal
|
||||||
|
className='modal-no-padding'
|
||||||
|
width={window.innerWidth * 0.8}
|
||||||
|
title={'日志'}
|
||||||
|
visible={true}
|
||||||
|
maskClosable={false}
|
||||||
|
centered={true}
|
||||||
|
onOk={async () => {
|
||||||
|
let result = await request.delete(`/jobs/${this.state.selectedRow['id']}/logs`);
|
||||||
|
if (result['code'] === 1) {
|
||||||
|
this.setState({
|
||||||
|
logVisible: false,
|
||||||
|
selectedRow: undefined
|
||||||
|
})
|
||||||
|
message.success('日志清空成功');
|
||||||
|
} else {
|
||||||
|
message.error(result['message'], 10);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
this.setState({
|
||||||
|
logVisible: false,
|
||||||
|
selectedRow: undefined
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
okText='清空'
|
||||||
|
okType={'danger'}
|
||||||
|
cancelText='取消'
|
||||||
|
>
|
||||||
|
<Timeline pending={this.state.logPending} mode={'left'}>
|
||||||
|
<pre className='cron-log'>
|
||||||
|
{
|
||||||
|
this.state.logs.map(item => {
|
||||||
|
|
||||||
|
return <><Divider
|
||||||
|
orientation="left"
|
||||||
|
style={{color: 'white'}}>{item['timestamp']}</Divider>{item['message']}</>;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
</Timeline>
|
||||||
|
</Modal> : undefined
|
||||||
|
}
|
||||||
</Content>
|
</Content>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
62
web/src/components/job/JobModal.js
Normal file
62
web/src/components/job/JobModal.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {Form, Input, Modal, Radio, Select} from "antd/lib/index";
|
||||||
|
|
||||||
|
const JobModal = ({title, visible, handleOk, handleCancel, confirmLoading, model}) => {
|
||||||
|
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const formItemLayout = {
|
||||||
|
labelCol: {span: 6},
|
||||||
|
wrapperCol: {span: 14},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={title}
|
||||||
|
visible={visible}
|
||||||
|
maskClosable={false}
|
||||||
|
centered={true}
|
||||||
|
onOk={() => {
|
||||||
|
form
|
||||||
|
.validateFields()
|
||||||
|
.then(values => {
|
||||||
|
form.resetFields();
|
||||||
|
handleOk(values);
|
||||||
|
})
|
||||||
|
.catch(info => {
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
confirmLoading={confirmLoading}
|
||||||
|
okText='确定'
|
||||||
|
cancelText='取消'
|
||||||
|
>
|
||||||
|
|
||||||
|
<Form form={form} {...formItemLayout} initialValues={model}>
|
||||||
|
<Form.Item name='id' noStyle>
|
||||||
|
<Input hidden={true}/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="任务类型" name='func' rules={[{required: true, message: '请选择任务类型'}]}>
|
||||||
|
<Select onChange={(value) => {
|
||||||
|
|
||||||
|
}}>
|
||||||
|
<Select.Option value="shell">Shell脚本</Select.Option>
|
||||||
|
<Select.Option value="check-asset-status-job">资产状态检测</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="任务名称" name='name' rules={[{required: true, message: '请输入任务名称'}]}>
|
||||||
|
<Input autoComplete="off" placeholder="请输入任务名称"/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="cron表达式" name='cron' rules={[{required: true, message: '请输入cron表达式'}]}>
|
||||||
|
<Input placeholder="请输入cron表达式"/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default JobModal;
|
@ -477,7 +477,7 @@ class OfflineSession extends Component {
|
|||||||
{
|
{
|
||||||
this.state.playbackVisible ?
|
this.state.playbackVisible ?
|
||||||
<Modal
|
<Modal
|
||||||
className='monitor'
|
className='modal-no-padding'
|
||||||
title="会话回放"
|
title="会话回放"
|
||||||
centered={true}
|
centered={true}
|
||||||
visible={this.state.playbackVisible}
|
visible={this.state.playbackVisible}
|
||||||
|
@ -473,7 +473,7 @@ class OnlineSession extends Component {
|
|||||||
{
|
{
|
||||||
this.state.accessVisible ?
|
this.state.accessVisible ?
|
||||||
<Modal
|
<Modal
|
||||||
className='monitor'
|
className='modal-no-padding'
|
||||||
title={this.state.sessionTitle}
|
title={this.state.sessionTitle}
|
||||||
centered={true}
|
centered={true}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
|
Loading…
Reference in New Issue
Block a user