完成计划任务功能
This commit is contained in:
parent
2d06cd373f
commit
f81aedcac0
6
main.go
6
main.go
@ -24,7 +24,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Version = "v0.2.7"
|
const Version = "v0.3.0"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.Fatal(Run())
|
log.Fatal(Run())
|
||||||
@ -182,6 +182,7 @@ func Run() error {
|
|||||||
})
|
})
|
||||||
global.Store = global.NewStore()
|
global.Store = global.NewStore()
|
||||||
global.Cron = cron.New(cron.WithSeconds()) //精确到秒
|
global.Cron = cron.New(cron.WithSeconds()) //精确到秒
|
||||||
|
global.Cron.Start()
|
||||||
|
|
||||||
jobs, err := model.FindJobByFunc(model.FuncCheckAssetStatusJob)
|
jobs, err := model.FindJobByFunc(model.FuncCheckAssetStatusJob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -193,6 +194,7 @@ func Run() error {
|
|||||||
Name: "资产状态检测",
|
Name: "资产状态检测",
|
||||||
Func: model.FuncCheckAssetStatusJob,
|
Func: model.FuncCheckAssetStatusJob,
|
||||||
Cron: "0 0 0/1 * * ?",
|
Cron: "0 0 0/1 * * ?",
|
||||||
|
Mode: model.JobModeAll,
|
||||||
Status: model.JobStatusRunning,
|
Status: model.JobStatusRunning,
|
||||||
Created: utils.NowJsonTime(),
|
Created: utils.NowJsonTime(),
|
||||||
Updated: utils.NowJsonTime(),
|
Updated: utils.NowJsonTime(),
|
||||||
@ -200,6 +202,7 @@ func Run() error {
|
|||||||
if err := model.CreateNewJob(&job); err != nil {
|
if err := model.CreateNewJob(&job); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
logrus.Debugf("创建计划任务「%v」cron「%v」", job.Name, job.Cron)
|
||||||
} else {
|
} else {
|
||||||
for i := range jobs {
|
for i := range jobs {
|
||||||
if jobs[i].Status == model.JobStatusRunning {
|
if jobs[i].Status == model.JobStatusRunning {
|
||||||
@ -207,6 +210,7 @@ func Run() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
logrus.Debugf("启动计划任务「%v」cron「%v」", jobs[i].Name, jobs[i].Cron)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"next-terminal/pkg/model"
|
"next-terminal/pkg/model"
|
||||||
|
"next-terminal/pkg/utils"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -13,6 +14,9 @@ func JobCreateEndpoint(c echo.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item.ID = utils.UUID()
|
||||||
|
item.Created = utils.NowJsonTime()
|
||||||
|
|
||||||
if err := model.CreateNewJob(&item); err != nil {
|
if err := model.CreateNewJob(&item); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ func ErrorHandler(next echo.HandlerFunc) echo.HandlerFunc {
|
|||||||
|
|
||||||
func Auth(next echo.HandlerFunc) echo.HandlerFunc {
|
func Auth(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
|
||||||
urls := []string{"download", "recording", "login", "static", "favicon", "logo", "asciinema"}
|
urls := []string{"/download", "/recording", "/login", "/static", "/favicon.ico", "/logo.svg", "/asciinema"}
|
||||||
|
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
// 路由拦截 - 登录身份、资源权限判断等
|
// 路由拦截 - 登录身份、资源权限判断等
|
||||||
@ -35,7 +35,7 @@ func Auth(next echo.HandlerFunc) echo.HandlerFunc {
|
|||||||
if c.Request().RequestURI == "/" || strings.HasPrefix(c.Request().RequestURI, "/#") {
|
if c.Request().RequestURI == "/" || strings.HasPrefix(c.Request().RequestURI, "/#") {
|
||||||
return next(c)
|
return next(c)
|
||||||
}
|
}
|
||||||
if strings.Contains(c.Request().RequestURI, urls[i]) {
|
if strings.HasPrefix(c.Request().RequestURI, urls[i]) {
|
||||||
return next(c)
|
return next(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,10 +46,8 @@ func SessionPagingEndpoint(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if utils.FileExists(recording) {
|
if utils.FileExists(recording) {
|
||||||
logrus.Debugf("检测到录屏文件[%v]存在", recording)
|
|
||||||
items[i].Recording = "1"
|
items[i].Recording = "1"
|
||||||
} else {
|
} else {
|
||||||
logrus.Warnf("检测到录屏文件[%v]不存在", recording)
|
|
||||||
items[i].Recording = "0"
|
items[i].Recording = "0"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -2,7 +2,6 @@ package handle
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"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"
|
||||||
@ -14,50 +13,55 @@ import (
|
|||||||
func RunTicker() {
|
func RunTicker() {
|
||||||
|
|
||||||
// 每隔一小时删除一次未使用的会话信息
|
// 每隔一小时删除一次未使用的会话信息
|
||||||
_, _ = global.Cron.AddFunc("0 0 0/1 * * ?", func() {
|
unUsedSessionTicker := time.NewTicker(time.Minute * 60)
|
||||||
sessions, _ := model.FindSessionByStatusIn([]string{model.NoConnect, model.Connecting})
|
go func() {
|
||||||
if sessions != nil && len(sessions) > 0 {
|
for range unUsedSessionTicker.C {
|
||||||
now := time.Now()
|
sessions, _ := model.FindSessionByStatusIn([]string{model.NoConnect, model.Connecting})
|
||||||
for i := range sessions {
|
if sessions != nil && len(sessions) > 0 {
|
||||||
if now.Sub(sessions[i].ConnectedTime.Time) > time.Hour*1 {
|
now := time.Now()
|
||||||
_ = model.DeleteSessionById(sessions[i].ID)
|
for i := range sessions {
|
||||||
s := sessions[i].Username + "@" + sessions[i].IP + ":" + strconv.Itoa(sessions[i].Port)
|
if now.Sub(sessions[i].ConnectedTime.Time) > time.Hour*1 {
|
||||||
logrus.Infof("会话「%v」ID「%v」超过1小时未打开,已删除。", s, sessions[i].ID)
|
_ = 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.AddFunc("0 0 0 * * ?", func() {
|
timeoutSessionTicker := time.NewTicker(time.Hour * 24)
|
||||||
property, err := model.FindPropertyByName("session-saved-limit")
|
go func() {
|
||||||
if err != nil {
|
for range timeoutSessionTicker.C {
|
||||||
return
|
property, err := model.FindPropertyByName("session-saved-limit")
|
||||||
}
|
|
||||||
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 {
|
if err != nil {
|
||||||
logrus.Errorf("删除离线会话失败 %v", err)
|
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunDataFix() {
|
func RunDataFix() {
|
||||||
|
@ -48,6 +48,21 @@ func FindAllAsset() (o []Asset, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FindAssetByIds(assetIds []string) (o []Asset, err error) {
|
||||||
|
err = global.DB.Where("id in ?", assetIds).Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindAssetByProtocol(protocol string) (o []Asset, err error) {
|
||||||
|
err = global.DB.Where("protocol = ?", protocol).Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindAssetByProtocolAndIds(protocol string, assetIds []string) (o []Asset, err error) {
|
||||||
|
err = global.DB.Where("protocol = ? and id in ?", protocol, assetIds).Find(&o).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func FindAssetByConditions(protocol string, account User) (o []Asset, err error) {
|
func FindAssetByConditions(protocol string, account User) (o []Asset, err error) {
|
||||||
db := global.DB.Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.owner,assets.created, users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on assets.owner = users.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id")
|
db := global.DB.Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.owner,assets.created, users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on assets.owner = users.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id")
|
||||||
|
|
||||||
|
249
pkg/model/job.go
249
pkg/model/job.go
@ -1,12 +1,15 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"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/term"
|
||||||
"next-terminal/pkg/utils"
|
"next-terminal/pkg/utils"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,18 +18,24 @@ const (
|
|||||||
JobStatusNotRunning = "not-running"
|
JobStatusNotRunning = "not-running"
|
||||||
|
|
||||||
FuncCheckAssetStatusJob = "check-asset-status-job"
|
FuncCheckAssetStatusJob = "check-asset-status-job"
|
||||||
|
FuncShellJob = "shell-job"
|
||||||
|
|
||||||
|
JobModeAll = "all"
|
||||||
|
JobModeCustom = "custom"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Job struct {
|
type Job struct {
|
||||||
ID string `gorm:"primary_key" json:"id"`
|
ID string `gorm:"primary_key" json:"id"`
|
||||||
CronJobId int `json:"cronJobId"`
|
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"`
|
Mode string `json:"mode"`
|
||||||
Metadata string `json:"metadata"`
|
ResourceIds string `json:"resourceIds"`
|
||||||
Created utils.JsonTime `json:"created"`
|
Status string `json:"status"`
|
||||||
Updated utils.JsonTime `json:"updated"`
|
Metadata string `json:"metadata"`
|
||||||
|
Created utils.JsonTime `json:"created"`
|
||||||
|
Updated utils.JsonTime `json:"updated"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Job) TableName() string {
|
func (r *Job) TableName() string {
|
||||||
@ -69,7 +78,7 @@ func FindJobByFunc(function string) (o []Job, err error) {
|
|||||||
func CreateNewJob(o *Job) (err error) {
|
func CreateNewJob(o *Job) (err error) {
|
||||||
|
|
||||||
if o.Status == JobStatusRunning {
|
if o.Status == JobStatusRunning {
|
||||||
j, err := getJob(o.ID, o.Func)
|
j, err := getJob(o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -103,7 +112,7 @@ func ChangeJobStatusById(id, status string) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if status == JobStatusRunning {
|
if status == JobStatusRunning {
|
||||||
j, err := getJob(job.ID, job.Func)
|
j, err := getJob(&job)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -111,10 +120,12 @@ func ChangeJobStatusById(id, status string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
job.CronJobId = int(entryID)
|
logrus.Debugf("开启计划任务「%v」,运行中计划任务数量「%v」", job.Name, len(global.Cron.Entries()))
|
||||||
return global.DB.Updates(Job{ID: id, Status: JobStatusRunning}).Error
|
|
||||||
|
return global.DB.Updates(Job{ID: id, Status: JobStatusRunning, CronJobId: int(entryID)}).Error
|
||||||
} else {
|
} else {
|
||||||
global.Cron.Remove(cron.EntryID(job.CronJobId))
|
global.Cron.Remove(cron.EntryID(job.CronJobId))
|
||||||
|
logrus.Debugf("关闭计划任务「%v」,运行中计划任务数量「%v」", job.Name, len(global.Cron.Entries()))
|
||||||
return global.DB.Updates(Job{ID: id, Status: JobStatusNotRunning}).Error
|
return global.DB.Updates(Job{ID: id, Status: JobStatusNotRunning}).Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,7 +135,7 @@ func ExecJobById(id string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
j, err := getJob(id, job.Func)
|
j, err := getJob(&job)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -150,10 +161,12 @@ func DeleteJobById(id string) error {
|
|||||||
return global.DB.Where("id = ?", 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(j *Job) (job cron.Job, err error) {
|
||||||
switch function {
|
switch j.Func {
|
||||||
case FuncCheckAssetStatusJob:
|
case FuncCheckAssetStatusJob:
|
||||||
job = CheckAssetStatusJob{ID: id}
|
job = CheckAssetStatusJob{ID: j.ID, Mode: j.Mode, ResourceIds: j.ResourceIds, Metadata: j.Metadata}
|
||||||
|
case FuncShellJob:
|
||||||
|
job = ShellJob{ID: j.ID, Mode: j.Mode, ResourceIds: j.ResourceIds, Metadata: j.Metadata}
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("未识别的任务")
|
return nil, errors.New("未识别的任务")
|
||||||
}
|
}
|
||||||
@ -161,44 +174,176 @@ func getJob(id, function string) (job cron.Job, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CheckAssetStatusJob struct {
|
type CheckAssetStatusJob struct {
|
||||||
ID string
|
ID string
|
||||||
|
Mode string
|
||||||
|
ResourceIds string
|
||||||
|
Metadata string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r CheckAssetStatusJob) Run() {
|
func (r CheckAssetStatusJob) Run() {
|
||||||
assets, _ := FindAllAsset()
|
if r.ID == "" {
|
||||||
if assets != nil && len(assets) > 0 {
|
return
|
||||||
|
|
||||||
msgChan := make(chan string)
|
|
||||||
for i := range assets {
|
|
||||||
asset := assets[i]
|
|
||||||
go func() {
|
|
||||||
t1 := time.Now()
|
|
||||||
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)
|
|
||||||
logrus.Infof(msg)
|
|
||||||
msgChan <- msg
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.ID != "" {
|
|
||||||
var message = ""
|
|
||||||
for i := 0; i < len(assets); i++ {
|
|
||||||
message += <-msgChan + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = UpdateJonUpdatedById(r.ID)
|
|
||||||
jobLog := JobLog{
|
|
||||||
ID: utils.UUID(),
|
|
||||||
JobId: r.ID,
|
|
||||||
Timestamp: utils.NowJsonTime(),
|
|
||||||
Message: message,
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = CreateNewJobLog(&jobLog)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var assets []Asset
|
||||||
|
if r.Mode == JobModeAll {
|
||||||
|
assets, _ = FindAllAsset()
|
||||||
|
} else {
|
||||||
|
assets, _ = FindAssetByIds(strings.Split(r.ResourceIds, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
if assets == nil || len(assets) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msgChan := make(chan string)
|
||||||
|
for i := range assets {
|
||||||
|
asset := assets[i]
|
||||||
|
go func() {
|
||||||
|
t1 := time.Now()
|
||||||
|
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)
|
||||||
|
logrus.Infof(msg)
|
||||||
|
msgChan <- msg
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = ""
|
||||||
|
for i := 0; i < len(assets); i++ {
|
||||||
|
message += <-msgChan + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = UpdateJonUpdatedById(r.ID)
|
||||||
|
jobLog := JobLog{
|
||||||
|
ID: utils.UUID(),
|
||||||
|
JobId: r.ID,
|
||||||
|
Timestamp: utils.NowJsonTime(),
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = CreateNewJobLog(&jobLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShellJob struct {
|
||||||
|
ID string
|
||||||
|
Mode string
|
||||||
|
ResourceIds string
|
||||||
|
Metadata string
|
||||||
|
}
|
||||||
|
|
||||||
|
type MetadataShell struct {
|
||||||
|
Shell string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ShellJob) Run() {
|
||||||
|
if r.ID == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var assets []Asset
|
||||||
|
if r.Mode == JobModeAll {
|
||||||
|
assets, _ = FindAssetByProtocol("ssh")
|
||||||
|
} else {
|
||||||
|
assets, _ = FindAssetByProtocolAndIds("ssh", strings.Split(r.ResourceIds, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
if assets == nil || len(assets) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadataShell MetadataShell
|
||||||
|
err := json.Unmarshal([]byte(r.Metadata), &metadataShell)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("JSON数据解析失败 %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msgChan := make(chan string)
|
||||||
|
for i := range assets {
|
||||||
|
asset, err := FindAssetById(assets[i].ID)
|
||||||
|
if err != nil {
|
||||||
|
msgChan <- fmt.Sprintf("资产「%v」Shell执行失败,查询数据异常「%v」", assets[i].Name, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
username = asset.Username
|
||||||
|
password = asset.Password
|
||||||
|
privateKey = asset.PrivateKey
|
||||||
|
passphrase = asset.Passphrase
|
||||||
|
ip = asset.IP
|
||||||
|
port = asset.Port
|
||||||
|
)
|
||||||
|
|
||||||
|
if asset.AccountType == "credential" {
|
||||||
|
credential, err := FindCredentialById(asset.CredentialId)
|
||||||
|
if err != nil {
|
||||||
|
msgChan <- fmt.Sprintf("资产「%v」Shell执行失败,查询授权凭证数据异常「%v」", assets[i].Name, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if credential.Type == Custom {
|
||||||
|
username = credential.Username
|
||||||
|
password = credential.Password
|
||||||
|
} else {
|
||||||
|
username = credential.Username
|
||||||
|
privateKey = credential.PrivateKey
|
||||||
|
passphrase = credential.Passphrase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
t1 := time.Now()
|
||||||
|
result, err := ExecCommandBySSH(metadataShell.Shell, ip, port, username, password, privateKey, passphrase)
|
||||||
|
elapsed := time.Since(t1)
|
||||||
|
var msg string
|
||||||
|
if err != nil {
|
||||||
|
msg = fmt.Sprintf("资产「%v」Shell执行失败,返回值「%v」,耗时「%v」", asset.Name, err.Error(), elapsed)
|
||||||
|
logrus.Infof(msg)
|
||||||
|
} else {
|
||||||
|
msg = fmt.Sprintf("资产「%v」Shell执行成功,返回值「%v」,耗时「%v」", asset.Name, result, elapsed)
|
||||||
|
logrus.Infof(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
msgChan <- msg
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = ""
|
||||||
|
for i := 0; i < len(assets); i++ {
|
||||||
|
message += <-msgChan + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = UpdateJonUpdatedById(r.ID)
|
||||||
|
jobLog := JobLog{
|
||||||
|
ID: utils.UUID(),
|
||||||
|
JobId: r.ID,
|
||||||
|
Timestamp: utils.NowJsonTime(),
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = CreateNewJobLog(&jobLog)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExecCommandBySSH(cmd, ip string, port int, username, password, privateKey, passphrase string) (result string, err error) {
|
||||||
|
sshClient, err := term.NewSshClient(ip, port, username, password, privateKey, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := sshClient.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
//执行远程命令
|
||||||
|
combo, err := session.CombinedOutput(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(combo), nil
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "next-terminal",
|
"name": "next-terminal",
|
||||||
"version": "0.2.7",
|
"version": "0.3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^4.3.0",
|
"@ant-design/icons": "^4.3.0",
|
||||||
|
@ -490,7 +490,7 @@ class Asset extends Component {
|
|||||||
if (tags[i] === '-') {
|
if (tags[i] === '-') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tagDocuments.push(<Tag>{tagArr[i]}</Tag>)
|
tagDocuments.push(<Tag key={tagArr[i]}>{tagArr[i]}</Tag>)
|
||||||
}
|
}
|
||||||
return tagDocuments;
|
return tagDocuments;
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,10 @@ import {
|
|||||||
PageHeader,
|
PageHeader,
|
||||||
Row,
|
Row,
|
||||||
Space,
|
Space,
|
||||||
|
Spin,
|
||||||
Switch,
|
Switch,
|
||||||
Table,
|
Table,
|
||||||
Tag,
|
Tag,
|
||||||
Timeline,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Typography
|
Typography
|
||||||
} from "antd";
|
} from "antd";
|
||||||
@ -68,6 +68,7 @@ class Job extends Component {
|
|||||||
modalConfirmLoading: false,
|
modalConfirmLoading: false,
|
||||||
selectedRow: undefined,
|
selectedRow: undefined,
|
||||||
selectedRowKeys: [],
|
selectedRowKeys: [],
|
||||||
|
logPending: false,
|
||||||
logs: []
|
logs: []
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -159,11 +160,19 @@ class Job extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
showModal(title, assets = null) {
|
showModal(title, obj = null) {
|
||||||
|
if (obj['func'] === 'shell-job') {
|
||||||
|
obj['shell'] = JSON.parse(obj['metadata'])['shell'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj['mode'] === 'custom') {
|
||||||
|
obj['resourceIds'] = obj['resourceIds'].split(',');
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
modalTitle: title,
|
modalTitle: title,
|
||||||
modalVisible: true,
|
modalVisible: true,
|
||||||
model: assets
|
model: obj
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -180,6 +189,19 @@ class Job extends Component {
|
|||||||
modalConfirmLoading: true
|
modalConfirmLoading: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(formData)
|
||||||
|
if (formData['func'] === 'shell-job') {
|
||||||
|
console.log(formData['shell'], JSON.stringify({'shell': formData['shell']}))
|
||||||
|
|
||||||
|
formData['metadata'] = JSON.stringify({'shell': formData['shell']});
|
||||||
|
formData['shell'] = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData['mode'] === 'custom') {
|
||||||
|
let resourceIds = formData['resourceIds'];
|
||||||
|
formData['resourceIds'] = resourceIds.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
if (formData.id) {
|
if (formData.id) {
|
||||||
// 向后台提交数据
|
// 向后台提交数据
|
||||||
const result = await request.put('/jobs/' + formData.id, formData);
|
const result = await request.put('/jobs/' + formData.id, formData);
|
||||||
@ -284,7 +306,9 @@ class Job extends Component {
|
|||||||
render: (func, record) => {
|
render: (func, record) => {
|
||||||
switch (func) {
|
switch (func) {
|
||||||
case "check-asset-status-job":
|
case "check-asset-status-job":
|
||||||
return <Tag color="green">资产状态检测</Tag>
|
return <Tag color="green">资产状态检测</Tag>;
|
||||||
|
case "shell-job":
|
||||||
|
return <Tag color="volcano">Shell脚本</Tag>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
@ -307,6 +331,9 @@ class Job extends Component {
|
|||||||
dataIndex: 'updated',
|
dataIndex: 'updated',
|
||||||
key: 'updated',
|
key: 'updated',
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
|
if (text === '0001-01-01 00:00:00') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Tooltip title={text}>
|
<Tooltip title={text}>
|
||||||
{dayjs(text).fromNow()}
|
{dayjs(text).fromNow()}
|
||||||
@ -330,7 +357,7 @@ class Job extends Component {
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
logVisible: true,
|
logVisible: true,
|
||||||
logPending: '正在加载...'
|
logPending: true
|
||||||
})
|
})
|
||||||
|
|
||||||
let result = await request.get(`/jobs/${record['id']}/logs`);
|
let result = await request.get(`/jobs/${record['id']}/logs`);
|
||||||
@ -401,7 +428,7 @@ class Job extends Component {
|
|||||||
<>
|
<>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
className="site-page-header-ghost-wrapper page-herder"
|
className="site-page-header-ghost-wrapper page-herder"
|
||||||
title="定时任务"
|
title="计划任务"
|
||||||
breadcrumb={{
|
breadcrumb={{
|
||||||
routes: routes,
|
routes: routes,
|
||||||
itemRender: itemRender
|
itemRender: itemRender
|
||||||
@ -409,7 +436,7 @@ class Job extends Component {
|
|||||||
extra={[
|
extra={[
|
||||||
<Logout key='logout'/>
|
<Logout key='logout'/>
|
||||||
]}
|
]}
|
||||||
subTitle="定时任务"
|
subTitle="计划任务"
|
||||||
>
|
>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
@ -444,7 +471,7 @@ class Job extends Component {
|
|||||||
|
|
||||||
<Tooltip title="新增">
|
<Tooltip title="新增">
|
||||||
<Button type="dashed" icon={<PlusOutlined/>}
|
<Button type="dashed" icon={<PlusOutlined/>}
|
||||||
onClick={() => this.showModal('新增任务', {})}>
|
onClick={() => this.showModal('新增计划任务', {})}>
|
||||||
|
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -548,7 +575,7 @@ class Job extends Component {
|
|||||||
okType={'danger'}
|
okType={'danger'}
|
||||||
cancelText='取消'
|
cancelText='取消'
|
||||||
>
|
>
|
||||||
<Timeline pending={this.state.logPending} mode={'left'}>
|
<Spin tip='加载中...' spinning={this.state.logPending}>
|
||||||
<pre className='cron-log'>
|
<pre className='cron-log'>
|
||||||
{
|
{
|
||||||
this.state.logs.map(item => {
|
this.state.logs.map(item => {
|
||||||
@ -559,8 +586,7 @@ class Job extends Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
</pre>
|
</pre>
|
||||||
|
</Spin>
|
||||||
</Timeline>
|
|
||||||
</Modal> : undefined
|
</Modal> : undefined
|
||||||
}
|
}
|
||||||
</Content>
|
</Content>
|
||||||
|
@ -1,9 +1,41 @@
|
|||||||
import React from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import {Form, Input, Modal, Radio, Select} from "antd/lib/index";
|
import {Form, Input, Modal, Radio, Select, Spin} from "antd/lib/index";
|
||||||
|
import TextArea from "antd/es/input/TextArea";
|
||||||
|
import request from "../../common/request";
|
||||||
|
import {message} from "antd";
|
||||||
|
|
||||||
const JobModal = ({title, visible, handleOk, handleCancel, confirmLoading, model}) => {
|
const JobModal = ({title, visible, handleOk, handleCancel, confirmLoading, model}) => {
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
if (model.func === undefined) {
|
||||||
|
model.func = 'shell-job';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.mode === undefined) {
|
||||||
|
model.mode = 'all';
|
||||||
|
}
|
||||||
|
|
||||||
|
let [func, setFunc] = useState(model.func);
|
||||||
|
let [mode, setMode] = useState(model.mode);
|
||||||
|
let [resources, setResources] = useState([]);
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
setResourcesLoading(true);
|
||||||
|
let result = await request.get('/assets?protocol=ssh');
|
||||||
|
if (result['code'] === 1) {
|
||||||
|
setResources(result['data']);
|
||||||
|
} else {
|
||||||
|
message.error(result['message'], 10);
|
||||||
|
}
|
||||||
|
setResourcesLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
let [resourcesLoading, setResourcesLoading] = useState(false);
|
||||||
|
|
||||||
const formItemLayout = {
|
const formItemLayout = {
|
||||||
labelCol: {span: 6},
|
labelCol: {span: 6},
|
||||||
@ -39,9 +71,9 @@ const JobModal = ({title, visible, handleOk, handleCancel, confirmLoading, model
|
|||||||
|
|
||||||
<Form.Item label="任务类型" name='func' rules={[{required: true, message: '请选择任务类型'}]}>
|
<Form.Item label="任务类型" name='func' rules={[{required: true, message: '请选择任务类型'}]}>
|
||||||
<Select onChange={(value) => {
|
<Select onChange={(value) => {
|
||||||
|
setFunc(value);
|
||||||
}}>
|
}}>
|
||||||
<Select.Option value="shell">Shell脚本</Select.Option>
|
<Select.Option value="shell-job">Shell脚本</Select.Option>
|
||||||
<Select.Option value="check-asset-status-job">资产状态检测</Select.Option>
|
<Select.Option value="check-asset-status-job">资产状态检测</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -50,10 +82,45 @@ const JobModal = ({title, visible, handleOk, handleCancel, confirmLoading, model
|
|||||||
<Input autoComplete="off" placeholder="请输入任务名称"/>
|
<Input autoComplete="off" placeholder="请输入任务名称"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
{
|
||||||
|
func === 'shell-job' ?
|
||||||
|
<Form.Item label="Shell脚本" name='shell' rules={[{required: true, message: '请输入Shell脚本'}]}>
|
||||||
|
<TextArea autoSize={{minRows: 5, maxRows: 10}} placeholder="在此处填写Shell脚本内容"/>
|
||||||
|
</Form.Item> : undefined
|
||||||
|
}
|
||||||
|
|
||||||
<Form.Item label="cron表达式" name='cron' rules={[{required: true, message: '请输入cron表达式'}]}>
|
<Form.Item label="cron表达式" name='cron' rules={[{required: true, message: '请输入cron表达式'}]}>
|
||||||
<Input placeholder="请输入cron表达式"/>
|
<Input placeholder="请输入cron表达式"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="资产选择" name='mode' rules={[{required: true, message: '请选择资产'}]}>
|
||||||
|
<Radio.Group onChange={async (e) => {
|
||||||
|
setMode(e.target.value);
|
||||||
|
}}>
|
||||||
|
<Radio value={'all'}>全部资产</Radio>
|
||||||
|
<Radio value={'custom'}>自定义</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{
|
||||||
|
mode === 'custom' ?
|
||||||
|
<Spin tip='加载中...' spinning={resourcesLoading}>
|
||||||
|
<Form.Item label="已选择资产" name='resourceIds' rules={[{required: true}]}>
|
||||||
|
<Select
|
||||||
|
mode="multiple"
|
||||||
|
allowClear
|
||||||
|
placeholder="请选择资产"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
resources.map(item => {
|
||||||
|
return <Select.Option key={item['id']}>{item['name']}</Select.Option>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Spin>
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user