完成计划任务功能
This commit is contained in:
@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"next-terminal/pkg/model"
|
||||
"next-terminal/pkg/utils"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@ -13,6 +14,9 @@ func JobCreateEndpoint(c echo.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
item.ID = utils.UUID()
|
||||
item.Created = utils.NowJsonTime()
|
||||
|
||||
if err := model.CreateNewJob(&item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ func ErrorHandler(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 {
|
||||
// 路由拦截 - 登录身份、资源权限判断等
|
||||
@ -35,7 +35,7 @@ func Auth(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
if c.Request().RequestURI == "/" || strings.HasPrefix(c.Request().RequestURI, "/#") {
|
||||
return next(c)
|
||||
}
|
||||
if strings.Contains(c.Request().RequestURI, urls[i]) {
|
||||
if strings.HasPrefix(c.Request().RequestURI, urls[i]) {
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
@ -46,10 +46,8 @@ func SessionPagingEndpoint(c echo.Context) error {
|
||||
}
|
||||
|
||||
if utils.FileExists(recording) {
|
||||
logrus.Debugf("检测到录屏文件[%v]存在", recording)
|
||||
items[i].Recording = "1"
|
||||
} else {
|
||||
logrus.Warnf("检测到录屏文件[%v]不存在", recording)
|
||||
items[i].Recording = "0"
|
||||
}
|
||||
} else {
|
||||
|
@ -2,7 +2,6 @@ package handle
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"next-terminal/pkg/global"
|
||||
"next-terminal/pkg/guacd"
|
||||
"next-terminal/pkg/model"
|
||||
"next-terminal/pkg/utils"
|
||||
@ -14,50 +13,55 @@ import (
|
||||
func RunTicker() {
|
||||
|
||||
// 每隔一小时删除一次未使用的会话信息
|
||||
_, _ = global.Cron.AddFunc("0 0 0/1 * * ?", func() {
|
||||
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)
|
||||
unUsedSessionTicker := time.NewTicker(time.Minute * 60)
|
||||
go func() {
|
||||
for range unUsedSessionTicker.C {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
// 每日凌晨删除超过时长限制的会话
|
||||
_, _ = 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)
|
||||
timeoutSessionTicker := time.NewTicker(time.Hour * 24)
|
||||
go func() {
|
||||
for range timeoutSessionTicker.C {
|
||||
property, err := model.FindPropertyByName("session-saved-limit")
|
||||
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() {
|
||||
|
@ -48,6 +48,21 @@ func FindAllAsset() (o []Asset, err error) {
|
||||
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) {
|
||||
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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/sirupsen/logrus"
|
||||
"next-terminal/pkg/global"
|
||||
"next-terminal/pkg/term"
|
||||
"next-terminal/pkg/utils"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -15,18 +18,24 @@ const (
|
||||
JobStatusNotRunning = "not-running"
|
||||
|
||||
FuncCheckAssetStatusJob = "check-asset-status-job"
|
||||
FuncShellJob = "shell-job"
|
||||
|
||||
JobModeAll = "all"
|
||||
JobModeCustom = "custom"
|
||||
)
|
||||
|
||||
type Job struct {
|
||||
ID string `gorm:"primary_key" json:"id"`
|
||||
CronJobId int `json:"cronJobId"`
|
||||
Name string `json:"name"`
|
||||
Func string `json:"func"`
|
||||
Cron string `json:"cron"`
|
||||
Status string `json:"status"`
|
||||
Metadata string `json:"metadata"`
|
||||
Created utils.JsonTime `json:"created"`
|
||||
Updated utils.JsonTime `json:"updated"`
|
||||
ID string `gorm:"primary_key" json:"id"`
|
||||
CronJobId int `json:"cronJobId"`
|
||||
Name string `json:"name"`
|
||||
Func string `json:"func"`
|
||||
Cron string `json:"cron"`
|
||||
Mode string `json:"mode"`
|
||||
ResourceIds string `json:"resourceIds"`
|
||||
Status string `json:"status"`
|
||||
Metadata string `json:"metadata"`
|
||||
Created utils.JsonTime `json:"created"`
|
||||
Updated utils.JsonTime `json:"updated"`
|
||||
}
|
||||
|
||||
func (r *Job) TableName() string {
|
||||
@ -69,7 +78,7 @@ func FindJobByFunc(function string) (o []Job, err error) {
|
||||
func CreateNewJob(o *Job) (err error) {
|
||||
|
||||
if o.Status == JobStatusRunning {
|
||||
j, err := getJob(o.ID, o.Func)
|
||||
j, err := getJob(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -103,7 +112,7 @@ func ChangeJobStatusById(id, status string) (err error) {
|
||||
return err
|
||||
}
|
||||
if status == JobStatusRunning {
|
||||
j, err := getJob(job.ID, job.Func)
|
||||
j, err := getJob(&job)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -111,10 +120,12 @@ func ChangeJobStatusById(id, status string) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
job.CronJobId = int(entryID)
|
||||
return global.DB.Updates(Job{ID: id, Status: JobStatusRunning}).Error
|
||||
logrus.Debugf("开启计划任务「%v」,运行中计划任务数量「%v」", job.Name, len(global.Cron.Entries()))
|
||||
|
||||
return global.DB.Updates(Job{ID: id, Status: JobStatusRunning, CronJobId: int(entryID)}).Error
|
||||
} else {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -124,7 +135,7 @@ func ExecJobById(id string) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
j, err := getJob(id, job.Func)
|
||||
j, err := getJob(&job)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -150,10 +161,12 @@ func DeleteJobById(id string) error {
|
||||
return global.DB.Where("id = ?", id).Delete(Job{}).Error
|
||||
}
|
||||
|
||||
func getJob(id, function string) (job cron.Job, err error) {
|
||||
switch function {
|
||||
func getJob(j *Job) (job cron.Job, err error) {
|
||||
switch j.Func {
|
||||
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:
|
||||
return nil, errors.New("未识别的任务")
|
||||
}
|
||||
@ -161,44 +174,176 @@ func getJob(id, function string) (job cron.Job, err error) {
|
||||
}
|
||||
|
||||
type CheckAssetStatusJob struct {
|
||||
ID string
|
||||
ID string
|
||||
Mode string
|
||||
ResourceIds string
|
||||
Metadata string
|
||||
}
|
||||
|
||||
func (r CheckAssetStatusJob) Run() {
|
||||
assets, _ := FindAllAsset()
|
||||
if assets != nil && len(assets) > 0 {
|
||||
|
||||
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)
|
||||
}
|
||||
if r.ID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
Reference in New Issue
Block a user