From 7d14d370574a406c6ecfb071de9cd20f0764aac4 Mon Sep 17 00:00:00 2001 From: ssongliu Date: Tue, 27 Sep 2022 17:26:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9C=8D=E5=8A=A1=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E9=87=8D=E6=96=B0=E6=8B=89=E8=B5=B7=E5=AE=9A=E6=97=B6=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/cronjob.go | 18 +++ backend/app/dto/cronjob.go | 5 +- backend/app/repo/cronjob.go | 31 ++++- backend/app/service/cornjob.go | 129 ++++++++++++++++--- backend/cron/cron.go | 23 ++-- backend/router/ro_cronjob.go | 1 + frontend/src/api/interface/cronjob.ts | 3 + frontend/src/api/modules/cronjob.ts | 4 + frontend/src/lang/modules/zh.ts | 3 + frontend/src/views/cronjob/index.vue | 4 +- frontend/src/views/cronjob/operate/index.vue | 64 ++++----- frontend/src/views/cronjob/record/index.vue | 16 ++- 12 files changed, 221 insertions(+), 80 deletions(-) diff --git a/backend/app/api/v1/cronjob.go b/backend/app/api/v1/cronjob.go index 3d6e34a43..b9b4a3f2f 100644 --- a/backend/app/api/v1/cronjob.go +++ b/backend/app/api/v1/cronjob.go @@ -111,6 +111,24 @@ func (b *BaseApi) UpdateCronjob(c *gin.Context) { helper.SuccessWithData(c, nil) } +func (b *BaseApi) UpdateCronjobStatus(c *gin.Context) { + var req dto.CronjobUpdateStatus + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + if err := cronjobService.UpdateStatus(req.ID, req.Status); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} + func (b *BaseApi) LoadRecordDetail(c *gin.Context) { var req dto.DetailFile if err := c.ShouldBindJSON(&req); err != nil { diff --git a/backend/app/dto/cronjob.go b/backend/app/dto/cronjob.go index 80bc33ec8..b0d0f860e 100644 --- a/backend/app/dto/cronjob.go +++ b/backend/app/dto/cronjob.go @@ -37,8 +37,11 @@ type CronjobUpdate struct { SourceDir string `json:"sourceDir"` TargetDirID int `json:"targetDirID"` RetainCopies int `json:"retainCopies" validate:"number,min=1"` +} - Status string `json:"status"` +type CronjobUpdateStatus struct { + ID uint `json:"id" validate:"required"` + Status string `json:"status" validate:"required"` } type DetailFile struct { diff --git a/backend/app/repo/cronjob.go b/backend/app/repo/cronjob.go index a3966d1be..d6319d536 100644 --- a/backend/app/repo/cronjob.go +++ b/backend/app/repo/cronjob.go @@ -13,6 +13,7 @@ type CronjobRepo struct{} type ICronjobRepo interface { Get(opts ...DBOption) (model.Cronjob, error) + List(opts ...DBOption) ([]model.Cronjob, error) Page(limit, offset int, opts ...DBOption) (int64, []model.Cronjob, error) Create(cronjob *model.Cronjob) error WithByDate(startTime, endTime time.Time) DBOption @@ -20,6 +21,7 @@ type ICronjobRepo interface { Save(id uint, cronjob model.Cronjob) error Update(id uint, vars map[string]interface{}) error Delete(opts ...DBOption) error + DeleteRecord(jobID uint) error StartRecords(cronjobID uint, targetPath string) model.JobRecords EndRecords(record model.JobRecords, status, message, records string) } @@ -38,28 +40,40 @@ func (u *CronjobRepo) Get(opts ...DBOption) (model.Cronjob, error) { return cronjob, err } -func (u *CronjobRepo) Page(page, size int, opts ...DBOption) (int64, []model.Cronjob, error) { - var users []model.Cronjob +func (u *CronjobRepo) List(opts ...DBOption) ([]model.Cronjob, error) { + var cronjobs []model.Cronjob db := global.DB.Model(&model.Cronjob{}) for _, opt := range opts { db = opt(db) } count := int64(0) db = db.Count(&count) - err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error - return count, users, err + err := db.Find(&cronjobs).Error + return cronjobs, err +} + +func (u *CronjobRepo) Page(page, size int, opts ...DBOption) (int64, []model.Cronjob, error) { + var cronjobs []model.Cronjob + db := global.DB.Model(&model.Cronjob{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Order("created_at").Limit(size).Offset(size * (page - 1)).Find(&cronjobs).Error + return count, cronjobs, err } func (u *CronjobRepo) PageRecords(page, size int, opts ...DBOption) (int64, []model.JobRecords, error) { - var users []model.JobRecords + var cronjobs []model.JobRecords db := global.DB.Model(&model.JobRecords{}) for _, opt := range opts { db = opt(db) } count := int64(0) db = db.Count(&count) - err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error - return count, users, err + err := db.Order("created_at").Limit(size).Offset(size * (page - 1)).Find(&cronjobs).Error + return count, cronjobs, err } func (u *CronjobRepo) Create(cronjob *model.Cronjob) error { @@ -112,3 +126,6 @@ func (u *CronjobRepo) Delete(opts ...DBOption) error { } return db.Delete(&model.Cronjob{}).Error } +func (u *CronjobRepo) DeleteRecord(jobID uint) error { + return global.DB.Where("cronjob_id = ?", jobID).Delete(&model.JobRecords{}).Error +} diff --git a/backend/app/service/cornjob.go b/backend/app/service/cornjob.go index 2e742e15d..daeb95783 100644 --- a/backend/app/service/cornjob.go +++ b/backend/app/service/cornjob.go @@ -19,11 +19,13 @@ import ( "github.com/1Panel-dev/1Panel/utils/cloud_storage" "github.com/jinzhu/copier" "github.com/pkg/errors" + "github.com/robfig/cron/v3" ) const ( errRecord = "errRecord" errHandle = "errHandle" + noRecord = "noRecord" ) type CronjobService struct{} @@ -33,6 +35,7 @@ type ICronjobService interface { SearchRecords(search dto.SearchRecord) (int64, interface{}, error) Create(cronjobDto dto.CronjobCreate) error Save(id uint, req dto.CronjobUpdate) error + UpdateStatus(id uint, status string) error Delete(ids []uint) error } @@ -93,19 +96,32 @@ func (u *CronjobService) Create(cronjobDto dto.CronjobCreate) error { if err := cronjobRepo.Create(&cronjob); err != nil { return err } + if err := u.StartJob(&cronjob); err != nil { + return err + } + return nil +} + +func (u *CronjobService) StartJob(cronjob *model.Cronjob) error { + global.Cron.Remove(cron.EntryID(cronjob.EntryID)) var ( entryID int err error ) - switch cronjobDto.Type { + switch cronjob.Type { case "shell": - entryID, err = u.AddShellJob(&cronjob) + entryID, err = u.AddShellJob(cronjob) case "curl": - entryID, err = u.AddCurlJob(&cronjob) + entryID, err = u.AddCurlJob(cronjob) case "directory": - entryID, err = u.AddDirectoryJob(&cronjob) + entryID, err = u.AddDirectoryJob(cronjob) + case "website": + entryID, err = u.AddWebSiteJob(cronjob) + case "database": + entryID, err = u.AddDatabaseJob(cronjob) + default: + entryID, err = u.AddShellJob(cronjob) } - if err != nil { return err } @@ -119,8 +135,25 @@ func (u *CronjobService) Delete(ids []uint) error { if cronjob.ID == 0 { return constant.ErrRecordNotFound } + global.Cron.Remove(cron.EntryID(cronjob.EntryID)) + _ = cronjobRepo.DeleteRecord(ids[0]) + + if err := os.RemoveAll(fmt.Sprintf("%s/%s/%s-%v", constant.TaskDir, cronjob.Type, cronjob.Name, cronjob.ID)); err != nil { + global.LOG.Errorf("rm file %s/%s/%s-%v failed, err: %v", constant.TaskDir, cronjob.Type, cronjob.Name, cronjob.ID, err) + } return cronjobRepo.Delete(commonRepo.WithByID(ids[0])) } + cronjobs, err := cronjobRepo.List(commonRepo.WithIdsIn(ids)) + if err != nil { + return err + } + for i := range cronjobs { + global.Cron.Remove(cron.EntryID(cronjobs[i].EntryID)) + _ = cronjobRepo.DeleteRecord(cronjobs[i].ID) + if err := os.RemoveAll(fmt.Sprintf("%s/%s/%s-%v", constant.TaskDir, cronjobs[i].Type, cronjobs[i].Name, cronjobs[i].ID)); err != nil { + global.LOG.Errorf("rm file %s/%s/%s-%v failed, err: %v", constant.TaskDir, cronjobs[i].Type, cronjobs[i].Name, cronjobs[i].ID, err) + } + } return cronjobRepo.Delete(commonRepo.WithIdsIn(ids)) } @@ -129,15 +162,33 @@ func (u *CronjobService) Save(id uint, req dto.CronjobUpdate) error { if err := copier.Copy(&cronjob, &req); err != nil { return errors.WithMessage(constant.ErrStructTransform, err.Error()) } + if err := u.StartJob(&cronjob); err != nil { + return err + } return cronjobRepo.Save(id, cronjob) } +func (u *CronjobService) UpdateStatus(id uint, status string) error { + cronjob, _ := cronjobRepo.Get(commonRepo.WithByID(id)) + if cronjob.ID == 0 { + return errors.WithMessage(constant.ErrRecordNotFound, "record not found") + } + if status == constant.StatusEnable { + if err := u.StartJob(&cronjob); err != nil { + return err + } + } else { + global.Cron.Remove(cron.EntryID(cronjob.EntryID)) + } + return cronjobRepo.Update(cronjob.ID, map[string]interface{}{"status": status}) +} + func (u *CronjobService) AddShellJob(cronjob *model.Cronjob) (int, error) { addFunc := func() { record := cronjobRepo.StartRecords(cronjob.ID, "") cmd := exec.Command(cronjob.Script) - stdout, err := cmd.Output() + stdout, err := cmd.CombinedOutput() if err != nil { record.Records = errHandle cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), errHandle) @@ -150,6 +201,7 @@ func (u *CronjobService) AddShellJob(cronjob *model.Cronjob) (int, error) { } cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records) } + global.LOG.Infof("add %s job %s successful", cronjob.Type, cronjob.Name) entryID, err := global.Cron.AddFunc(cronjob.Spec, addFunc) if err != nil { return 0, err @@ -183,6 +235,7 @@ func (u *CronjobService) AddCurlJob(cronjob *model.Cronjob) (int, error) { } cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records) } + global.LOG.Infof("add %s job %s successful", cronjob.Type, cronjob.Name) entryID, err := global.Cron.AddFunc(cronjob.Spec, addFunc) if err != nil { return 0, err @@ -209,6 +262,7 @@ func (u *CronjobService) AddDirectoryJob(cronjob *model.Cronjob) (int, error) { } cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records) } + global.LOG.Infof("add %s job %s successful", cronjob.Type, cronjob.Name) entryID, err := global.Cron.AddFunc(cronjob.Spec, addFunc) if err != nil { return 0, err @@ -228,6 +282,11 @@ func (u *CronjobService) AddWebSiteJob(cronjob *model.Cronjob) (int, error) { cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), errHandle) return } + if len(message) == 0 { + record.Records = noRecord + cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records) + return + } record.Records, err = mkdirAndWriteFile(cronjob, record.StartTime, message) if err != nil { record.Records = errRecord @@ -235,6 +294,34 @@ func (u *CronjobService) AddWebSiteJob(cronjob *model.Cronjob) (int, error) { } cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records) } + global.LOG.Infof("add %s job %s successful", cronjob.Type, cronjob.Name) + entryID, err := global.Cron.AddFunc(cronjob.Spec, addFunc) + if err != nil { + return 0, err + } + return int(entryID), nil +} + +func (u *CronjobService) AddDatabaseJob(cronjob *model.Cronjob) (int, error) { + addFunc := func() { + record := cronjobRepo.StartRecords(cronjob.ID, "") + if len(cronjob.URL) == 0 { + return + } + message, err := tarWithExclude(cronjob, record.StartTime) + if err != nil { + record.Records = errHandle + cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), errHandle) + return + } + record.Records, err = mkdirAndWriteFile(cronjob, record.StartTime, message) + if err != nil { + record.Records = errRecord + global.LOG.Errorf("save file %s failed, err: %v", record.Records, err) + } + cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records) + } + global.LOG.Infof("add %s job %s successful", cronjob.Type, cronjob.Name) entryID, err := global.Cron.AddFunc(cronjob.Spec, addFunc) if err != nil { return 0, err @@ -268,18 +355,27 @@ func tarWithExclude(cronjob *model.Cronjob, startTime time.Time) ([]byte, error) return nil, fmt.Errorf("load target dir failed, err: %v", err) } - excludes := strings.Split(cronjob.ExclusionRules, ";") - name := fmt.Sprintf("%s/%s.tar.gz", targetdir, startTime.Format("20060102150405")) - exStr := []string{"-zcvPf", name} - for _, exclude := range excludes { - exStr = append(exStr, "--exclude") - exStr = append(exStr, exclude) + exStr := []string{} + name := "" + if cronjob.Type == "database" { + exStr = append(exStr, "-zvPf") + name = fmt.Sprintf("%s/%s.gz", targetdir, startTime.Format("20060102150405")) + exStr = append(exStr, name) + } else { + exStr = append(exStr, "-zcvPf") + name = fmt.Sprintf("%s/%s.tar.gz", targetdir, startTime.Format("20060102150405")) + exStr = append(exStr, name) + excludes := strings.Split(cronjob.ExclusionRules, ";") + for _, exclude := range excludes { + exStr = append(exStr, "--exclude") + exStr = append(exStr, exclude) + } } exStr = append(exStr, cronjob.SourceDir) cmd := exec.Command("tar", exStr...) - stdout, err := cmd.Output() + stdout, err := cmd.CombinedOutput() if err != nil { - return nil, fmt.Errorf("tar zcvPf failed, err: %v", err) + return nil, fmt.Errorf("tar zcPf failed, err: %v", err) } if varMaps["type"] != "LOCAL" { @@ -291,6 +387,9 @@ func tarWithExclude(cronjob *model.Cronjob, startTime time.Time) ([]byte, error) if !isOK { return nil, fmt.Errorf("cloud storage upload failed, err: %v", err) } + if err := os.RemoveAll(fmt.Sprintf("%s/%s/%s-%v", constant.TmpDir, cronjob.Type, cronjob.Name, cronjob.ID)); err != nil { + global.LOG.Errorf("rm file %s/%s/%s-%v failed, err: %v", constant.TaskDir, cronjob.Type, cronjob.Name, cronjob.ID, err) + } } return stdout, nil } @@ -305,8 +404,8 @@ func loadTargetInfo(cronjob *model.Cronjob) (map[string]interface{}, string, err return nil, "", err } dir := "" + varMap["type"] = backup.Type if backup.Type != "LOCAL" { - varMap["type"] = backup.Type varMap["bucket"] = backup.Bucket switch backup.Type { case constant.Sftp: diff --git a/backend/cron/cron.go b/backend/cron/cron.go index 45fe441e5..057331f26 100644 --- a/backend/cron/cron.go +++ b/backend/cron/cron.go @@ -3,6 +3,9 @@ package cron import ( "time" + "github.com/1Panel-dev/1Panel/app/model" + "github.com/1Panel-dev/1Panel/app/service" + "github.com/1Panel-dev/1Panel/constant" "github.com/1Panel-dev/1Panel/cron/job" "github.com/1Panel-dev/1Panel/global" "github.com/robfig/cron/v3" @@ -10,15 +13,7 @@ import ( func Run() { nyc, _ := time.LoadLocation("Asia/Shanghai") - Cron := cron.New(cron.WithLocation(nyc)) - - // var Cronjobs []model.Cronjob - // if err := global.DB.Where("status = ?", constant.StatusEnable).Find(&Cronjobs).Error; err != nil { - // global.LOG.Errorf("start my cronjob failed, err: %v", err) - // } - // for _, cronjob := range Cronjobs { - // switch cronjob.Type {} - // } + Cron := cron.New(cron.WithLocation(nyc), cron.WithChain(cron.Recover(cron.DefaultLogger)), cron.WithChain(cron.DelayIfStillRunning(cron.DefaultLogger))) _, err := Cron.AddJob("@every 1m", job.NewMonitorJob()) if err != nil { global.LOG.Errorf("can not add corn job: %s", err.Error()) @@ -26,4 +21,14 @@ func Run() { Cron.Start() global.Cron = Cron + + var Cronjobs []model.Cronjob + if err := global.DB.Where("status = ?", constant.StatusEnable).Find(&Cronjobs).Error; err != nil { + global.LOG.Errorf("start my cronjob failed, err: %v", err) + } + for _, cronjob := range Cronjobs { + if err := service.ServiceGroupApp.StartJob(&cronjob); err != nil { + global.LOG.Errorf("start %s job %s failed, err: %v", cronjob.Type, cronjob.Name, err) + } + } } diff --git a/backend/router/ro_cronjob.go b/backend/router/ro_cronjob.go index 0cc30f426..bc711bb34 100644 --- a/backend/router/ro_cronjob.go +++ b/backend/router/ro_cronjob.go @@ -17,6 +17,7 @@ func (s *CronjobRouter) InitCronjobRouter(Router *gin.RouterGroup) { withRecordRouter.POST("", baseApi.CreateCronjob) withRecordRouter.POST("/del", baseApi.DeleteCronjob) withRecordRouter.PUT(":id", baseApi.UpdateCronjob) + withRecordRouter.POST("/status", baseApi.UpdateCronjobStatus) cmdRouter.POST("/search", baseApi.SearchCronjob) cmdRouter.POST("/search/records", baseApi.SearchJobRecords) cmdRouter.POST("/search/detail", baseApi.LoadRecordDetail) diff --git a/frontend/src/api/interface/cronjob.ts b/frontend/src/api/interface/cronjob.ts index 86078b6da..888fbf169 100644 --- a/frontend/src/api/interface/cronjob.ts +++ b/frontend/src/api/interface/cronjob.ts @@ -56,6 +56,9 @@ export namespace Cronjob { sourceDir: string; targetDirID: number; retainCopies: number; + } + export interface UpdateStatus { + id: number; status: string; } export interface SearchRecord extends ReqPage { diff --git a/frontend/src/api/modules/cronjob.ts b/frontend/src/api/modules/cronjob.ts index 5393d8a4f..462afce06 100644 --- a/frontend/src/api/modules/cronjob.ts +++ b/frontend/src/api/modules/cronjob.ts @@ -25,3 +25,7 @@ export const searchRecords = (params: Cronjob.SearchRecord) => { export const getRecordDetail = (params: string) => { return http.post(`cronjobs/search/detail`, { path: params }); }; + +export const updateStatus = (params: Cronjob.UpdateStatus) => { + return http.post(`cronjobs/status`, params); +}; diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 50e5bebf8..38a2ae5ed 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -183,6 +183,9 @@ export default { saturday: '周六', sunday: '周日', shellContent: '脚本内容', + errRecord: '错误的日志记录', + errHandle: '任务执行失败', + noRecord: '执行未产生任何日志', }, monitor: { avgLoad: '平均负载', diff --git a/frontend/src/views/cronjob/index.vue b/frontend/src/views/cronjob/index.vue index 00f0dfba7..32d96ab1d 100644 --- a/frontend/src/views/cronjob/index.vue +++ b/frontend/src/views/cronjob/index.vue @@ -72,7 +72,7 @@ import RecordDialog from '@/views/cronjob/record/index.vue'; import { loadZero } from '@/views/cronjob/options'; import { onMounted, reactive, ref } from 'vue'; import { loadBackupName } from '@/views/setting/helper'; -import { deleteCronjob, editCronjob, getCronjobPage } from '@/api/modules/cronjob'; +import { deleteCronjob, getCronjobPage, updateStatus } from '@/api/modules/cronjob'; import { loadWeek } from './options'; import i18n from '@/lang'; import { Cronjob } from '@/api/interface/cronjob'; @@ -149,7 +149,7 @@ const beforeChangeStatus = () => { }; const onChangeStatus = async (row: Cronjob.CronjobInfo) => { if (switchState.value) { - await editCronjob(row); + await updateStatus({ id: row.id, status: row.status }); ElMessage.success(i18n.global.t('commons.msg.operationSuccess')); search(); } diff --git a/frontend/src/views/cronjob/operate/index.vue b/frontend/src/views/cronjob/operate/index.vue index 9d45ff458..43e956766 100644 --- a/frontend/src/views/cronjob/operate/index.vue +++ b/frontend/src/views/cronjob/operate/index.vue @@ -8,7 +8,7 @@ @@ -17,12 +17,7 @@ - + @@ -56,12 +51,18 @@ - + @@ -86,7 +87,7 @@ prop="sourceDir" > - +