diff --git a/agent/app/api/v2/snapshot.go b/agent/app/api/v2/snapshot.go index 9eeb28753c42..f15d9a3a3968 100644 --- a/agent/app/api/v2/snapshot.go +++ b/agent/app/api/v2/snapshot.go @@ -36,7 +36,7 @@ func (b *BaseApi) CreateSnapshot(c *gin.Context) { return } - if err := snapshotService.SnapshotCreate(req); err != nil { + if err := snapshotService.SnapshotCreate(req, false); err != nil { helper.InternalServer(c, err) return } diff --git a/agent/app/dto/cronjob.go b/agent/app/dto/cronjob.go index 7787bf8fee21..404a1bee766f 100644 --- a/agent/app/dto/cronjob.go +++ b/agent/app/dto/cronjob.go @@ -134,6 +134,7 @@ type SearchRecord struct { type Record struct { ID uint `json:"id"` + TaskID string `json:"taskID"` StartTime string `json:"startTime"` Records string `json:"records"` Status string `json:"status"` diff --git a/agent/app/repo/cronjob.go b/agent/app/repo/cronjob.go index f6e9acf2dfca..28fda27a0fca 100644 --- a/agent/app/repo/cronjob.go +++ b/agent/app/repo/cronjob.go @@ -6,6 +6,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" + "github.com/google/uuid" "gorm.io/gorm" ) @@ -146,6 +147,7 @@ func (u *CronjobRepo) StartRecords(cronjobID uint, targetPath string) model.JobR var record model.JobRecords record.StartTime = time.Now() record.CronjobID = cronjobID + record.TaskID = uuid.New().String() record.Status = constant.StatusWaiting if err := global.DB.Create(&record).Error; err != nil { global.LOG.Errorf("create record status failed, err: %v", err) diff --git a/agent/app/service/cronjob_backup.go b/agent/app/service/cronjob_backup.go index c1197258f24c..bb3b6a0114ab 100644 --- a/agent/app/service/cronjob_backup.go +++ b/agent/app/service/cronjob_backup.go @@ -17,7 +17,7 @@ import ( "github.com/pkg/errors" ) -func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) error { +func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, taskID string) error { var apps []model.AppInstall if cronjob.AppID == "all" { apps, _ = appInstallRepo.ListBy() @@ -46,7 +46,7 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) e record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("app/%s/%s", app.App.Key, app.Name)) record.FileName = fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)) - if err := handleAppBackup(&app, nil, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret, ""); err != nil { + if err := handleAppBackup(&app, nil, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret, taskID); err != nil { return err } downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName)) @@ -63,7 +63,7 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) e return nil } -func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Time) error { +func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Time, taskID string) error { webs := loadWebsForJob(cronjob) if len(webs) == 0 { return errors.New("no such website in database!") @@ -82,7 +82,7 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s", web.PrimaryDomain)) record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", web.PrimaryDomain, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)) - if err := handleWebsiteBackup(&web, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret, ""); err != nil { + if err := handleWebsiteBackup(&web, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret, taskID); err != nil { return err } downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName)) @@ -99,7 +99,7 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim return nil } -func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Time) error { +func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Time, taskID string) error { dbs := loadDbsForJob(cronjob) if len(dbs) == 0 { return errors.New("no such db in database!") @@ -120,11 +120,11 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name)) record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)) if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" { - if err := handleMysqlBackup(dbInfo, nil, backupDir, record.FileName, ""); err != nil { + if err := handleMysqlBackup(dbInfo, nil, backupDir, record.FileName, taskID); err != nil { return err } } else { - if err := handlePostgresqlBackup(dbInfo, nil, backupDir, record.FileName, ""); err != nil { + if err := handlePostgresqlBackup(dbInfo, nil, backupDir, record.FileName, taskID); err != nil { return err } } @@ -212,11 +212,15 @@ func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.T return nil } -func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, startTime time.Time) error { +func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, startTime time.Time, taskID string) error { accountMap, err := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ",")) if err != nil { return err } + itemData, err := NewISnapshotService().LoadSnapshotData() + if err != nil { + return err + } var record model.BackupRecord record.From = "cronjob" @@ -227,14 +231,28 @@ func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, startTime time.Ti record.FileDir = "system_snapshot" versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion")) + scope := "core" + if !global.IsMaster { + scope = "agent" + } req := dto.SnapshotCreate{ - Name: fmt.Sprintf("snapshot-1panel-%s-linux-%s-%s", versionItem.Value, loadOs(), startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)), + Name: fmt.Sprintf("snapshot-1panel-%s-%s-linux-%s-%s", scope, versionItem.Value, loadOs(), startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)), Secret: cronjob.Secret, + TaskID: taskID, SourceAccountIDs: record.SourceAccountIDs, DownloadAccountID: cronjob.DownloadAccountID, + AppData: itemData.AppData, + PanelData: itemData.PanelData, + BackupData: itemData.BackupData, + WithMonitorData: true, + WithLoginLog: true, + WithOperationLog: true, + WithSystemLog: true, + WithTaskLog: true, } - if err := NewISnapshotService().HandleSnapshot(req); err != nil { + + if err := NewISnapshotService().SnapshotCreate(req, true); err != nil { return err } record.FileName = req.Name + ".tar.gz" diff --git a/agent/app/service/cronjob_helper.go b/agent/app/service/cronjob_helper.go index 7ee076b1f9d0..3a24e1fcbb03 100644 --- a/agent/app/service/cronjob_helper.go +++ b/agent/app/service/cronjob_helper.go @@ -10,6 +10,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" @@ -31,36 +32,26 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) { if len(cronjob.Script) == 0 { return } - record.Records = u.generateLogsPath(*cronjob, record.StartTime) - _ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records}) - err = u.handleShell(*cronjob, record.Records) - u.removeExpiredLog(*cronjob) + err = u.handleShell(*cronjob, record.TaskID) case "curl": if len(cronjob.URL) == 0 { return } - record.Records = u.generateLogsPath(*cronjob, record.StartTime) - _ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records}) - err = cmd.ExecShell(record.Records, 24*time.Hour, "bash", "-c", "curl", cronjob.URL) - u.removeExpiredLog(*cronjob) + err = u.handleCurl(*cronjob, record.TaskID) case "ntp": - err = u.handleNtpSync() - u.removeExpiredLog(*cronjob) + err = u.handleNtpSync(*cronjob, record.TaskID) case "cutWebsiteLog": var messageItem []string messageItem, record.File, err = u.handleCutWebsiteLog(cronjob, record.StartTime) message = []byte(strings.Join(messageItem, "\n")) case "clean": - messageItem := "" - messageItem, err = u.handleSystemClean() - message = []byte(messageItem) - u.removeExpiredLog(*cronjob) + err = u.handleSystemClean(*cronjob, record.TaskID) case "website": - err = u.handleWebsite(*cronjob, record.StartTime) + err = u.handleWebsite(*cronjob, record.StartTime, record.TaskID) case "app": - err = u.handleApp(*cronjob, record.StartTime) + err = u.handleApp(*cronjob, record.StartTime, record.TaskID) case "database": - err = u.handleDatabase(*cronjob, record.StartTime) + err = u.handleDatabase(*cronjob, record.StartTime, record.TaskID) case "directory": if len(cronjob.SourceDir) == 0 { return @@ -70,7 +61,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) { err = u.handleSystemLog(*cronjob, record.StartTime) case "snapshot": _ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records}) - err = u.handleSnapshot(*cronjob, record.StartTime) + err = u.handleSnapshot(*cronjob, record.StartTime, record.TaskID) } if err != nil { @@ -90,53 +81,95 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) { }() } -func (u *CronjobService) handleShell(cronjob model.Cronjob, logPath string) error { - if len(cronjob.ContainerName) != 0 { - command := "sh" - if len(cronjob.Command) != 0 { - command = cronjob.Command - } - scriptFile, _ := os.ReadFile(cronjob.Script) - return cmd.ExecShell(logPath, 24*time.Hour, "docker", "exec", cronjob.ContainerName, command, "-c", strings.ReplaceAll(string(scriptFile), "\"", "\\\"")) - } - if len(cronjob.Executor) == 0 { - cronjob.Executor = "bash" +func (u *CronjobService) handleShell(cronjob model.Cronjob, taskID string) error { + taskItem, err := task.NewTaskWithOps(fmt.Sprintf("cronjob-%s", cronjob.Name), task.TaskHandle, task.TaskScopeCronjob, taskID, cronjob.ID) + if err != nil { + global.LOG.Errorf("new task for exec shell failed, err: %v", err) + return err } - if cronjob.ScriptMode == "input" { - fileItem := pathUtils.Join(global.CONF.System.BaseDir, "1panel", "task", "shell", cronjob.Name, cronjob.Name+".sh") - _ = os.MkdirAll(pathUtils.Dir(fileItem), os.ModePerm) - shellFile, err := os.OpenFile(fileItem, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.FilePerm) - if err != nil { - return err + + taskItem.AddSubTask(i18n.GetWithName("HandleShell", cronjob.Name), func(t *task.Task) error { + if len(cronjob.ContainerName) != 0 { + command := "sh" + if len(cronjob.Command) != 0 { + command = cronjob.Command + } + scriptFile, _ := os.ReadFile(cronjob.Script) + return cmd.ExecShellWithTask(taskItem, 24*time.Hour, "docker", "exec", cronjob.ContainerName, command, "-c", strings.ReplaceAll(string(scriptFile), "\"", "\\\"")) } - defer shellFile.Close() - if _, err := shellFile.WriteString(cronjob.Script); err != nil { - return err + if len(cronjob.Executor) == 0 { + cronjob.Executor = "bash" + } + if cronjob.ScriptMode == "input" { + fileItem := pathUtils.Join(global.CONF.System.BaseDir, "1panel", "task", "shell", cronjob.Name, cronjob.Name+".sh") + _ = os.MkdirAll(pathUtils.Dir(fileItem), os.ModePerm) + shellFile, err := os.OpenFile(fileItem, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.FilePerm) + if err != nil { + return err + } + defer shellFile.Close() + if _, err := shellFile.WriteString(cronjob.Script); err != nil { + return err + } + if len(cronjob.User) == 0 { + return cmd.ExecShellWithTask(taskItem, 24*time.Hour, cronjob.Executor, fileItem) + } + return cmd.ExecShellWithTask(taskItem, 24*time.Hour, "sudo", "-u", cronjob.User, cronjob.Executor, fileItem) } if len(cronjob.User) == 0 { - return cmd.ExecShell(logPath, 24*time.Hour, cronjob.Executor, fileItem) + return cmd.ExecShellWithTask(taskItem, 24*time.Hour, cronjob.Executor, cronjob.Script) } - return cmd.ExecShell(logPath, 24*time.Hour, "sudo", "-u", cronjob.User, cronjob.Executor, fileItem) - } - if len(cronjob.User) == 0 { - return cmd.ExecShell(logPath, 24*time.Hour, cronjob.Executor, cronjob.Script) - } - return cmd.ExecShell(logPath, 24*time.Hour, "sudo", "-u", cronjob.User, cronjob.Executor, cronjob.Script) + if err := cmd.ExecShellWithTask(taskItem, 24*time.Hour, "sudo", "-u", cronjob.User, cronjob.Executor, cronjob.Script); err != nil { + return err + } + return nil + }, + nil, + ) + return taskItem.Execute() } -func (u *CronjobService) handleNtpSync() error { - ntpServer, err := settingRepo.Get(settingRepo.WithByKey("NtpSite")) +func (u *CronjobService) handleCurl(cronjob model.Cronjob, taskID string) error { + taskItem, err := task.NewTaskWithOps(fmt.Sprintf("cronjob-%s", cronjob.Name), task.TaskHandle, task.TaskScopeCronjob, taskID, cronjob.ID) if err != nil { + global.LOG.Errorf("new task for exec shell failed, err: %v", err) return err } - ntime, err := ntp.GetRemoteTime(ntpServer.Value) + + taskItem.AddSubTask(i18n.GetWithName("HandleShell", cronjob.Name), func(t *task.Task) error { + if err := cmd.ExecShellWithTask(taskItem, 24*time.Hour, "bash", "-c", "curl", cronjob.URL); err != nil { + return err + } + return nil + }, + nil, + ) + return taskItem.Execute() +} + +func (u *CronjobService) handleNtpSync(cronjob model.Cronjob, taskID string) error { + taskItem, err := task.NewTaskWithOps(fmt.Sprintf("cronjob-%s", cronjob.Name), task.TaskHandle, task.TaskScopeCronjob, taskID, cronjob.ID) if err != nil { + global.LOG.Errorf("new task for exec shell failed, err: %v", err) return err } - if err := ntp.UpdateSystemTime(ntime.Format(constant.DateTimeLayout)); err != nil { - return err - } - return nil + + taskItem.AddSubTask(i18n.GetMsgByKey("HandleNtpSync"), func(t *task.Task) error { + ntpServer, err := settingRepo.Get(settingRepo.WithByKey("NtpSite")) + if err != nil { + return err + } + taskItem.Logf("ntp server: %s", ntpServer.Value) + ntime, err := ntp.GetRemoteTime(ntpServer.Value) + if err != nil { + return err + } + if err := ntp.UpdateSystemTime(ntime.Format(constant.DateTimeLayout)); err != nil { + return err + } + return nil + }, nil) + return taskItem.Execute() } func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime time.Time) ([]string, string, error) { @@ -201,8 +234,13 @@ func backupLogFile(dstFilePath, websiteLogDir string, fileOp files.FileOp) error return nil } -func (u *CronjobService) handleSystemClean() (string, error) { - return NewIDeviceService().CleanForCronjob() +func (u *CronjobService) handleSystemClean(cronjob model.Cronjob, taskID string) error { + taskItem, err := task.NewTaskWithOps(fmt.Sprintf("cronjob-%s", cronjob.Name), task.TaskHandle, task.TaskScopeCronjob, taskID, cronjob.ID) + if err != nil { + global.LOG.Errorf("new task for system clean failed, err: %v", err) + return err + } + return systemClean(taskItem) } func (u *CronjobService) uploadCronjobBackFile(cronjob model.Cronjob, accountMap map[string]backupClientHelper, file string) (string, error) { @@ -274,16 +312,6 @@ func (u *CronjobService) removeExpiredLog(cronjob model.Cronjob) { } } -func (u *CronjobService) generateLogsPath(cronjob model.Cronjob, startTime time.Time) string { - dir := fmt.Sprintf("%s/task/%s/%s", constant.DataDir, cronjob.Type, cronjob.Name) - if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { - _ = os.MkdirAll(dir, os.ModePerm) - } - - path := fmt.Sprintf("%s/%s.log", dir, startTime.Format(constant.DateTimeSlimLayout)) - return path -} - func hasBackup(cronjobType string) bool { return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log" } diff --git a/agent/app/service/device.go b/agent/app/service/device.go index 6a2dccaab768..07f5d11e1cf8 100644 --- a/agent/app/service/device.go +++ b/agent/app/service/device.go @@ -41,7 +41,6 @@ type IDeviceService interface { Scan() dto.CleanData Clean(req []dto.Clean) - CleanForCronjob() (string, error) } func NewIDeviceService() IDeviceService { diff --git a/agent/app/service/device_clean.go b/agent/app/service/device_clean.go index 2a456d789b07..b98c5e50413b 100644 --- a/agent/app/service/device_clean.go +++ b/agent/app/service/device_clean.go @@ -10,11 +10,13 @@ import ( "time" "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/i18n" "github.com/1Panel-dev/1Panel/agent/utils/docker" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/task" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/common" @@ -290,64 +292,71 @@ func (u *DeviceService) Clean(req []dto.Clean) { } } -func (u *DeviceService) CleanForCronjob() (string, error) { - logs := "" - size := int64(0) - fileCount := 0 - dropFileOrDirWithLog(path.Join(global.CONF.System.BaseDir, "1panel_original"), &logs, &size, &fileCount) - - upgradePath := path.Join(global.CONF.System.BaseDir, upgradePath) - upgradeFiles, _ := os.ReadDir(upgradePath) - if len(upgradeFiles) != 0 { - sort.Slice(upgradeFiles, func(i, j int) bool { - return upgradeFiles[i].Name() > upgradeFiles[j].Name() - }) - for i := 0; i < len(upgradeFiles); i++ { - if i != 0 { - dropFileOrDirWithLog(path.Join(upgradePath, upgradeFiles[i].Name()), &logs, &size, &fileCount) +func systemClean(taskItem *task.Task) error { + taskItem.AddSubTask(i18n.GetMsgByKey("HandleSystemClean"), func(t *task.Task) error { + size := int64(0) + fileCount := 0 + dropWithTask(path.Join(global.CONF.System.BaseDir, "1panel_original"), taskItem, &size, &fileCount) + + upgradePath := path.Join(global.CONF.System.BaseDir, upgradePath) + upgradeFiles, _ := os.ReadDir(upgradePath) + if len(upgradeFiles) != 0 { + sort.Slice(upgradeFiles, func(i, j int) bool { + return upgradeFiles[i].Name() > upgradeFiles[j].Name() + }) + for i := 0; i < len(upgradeFiles); i++ { + if i != 0 { + dropWithTask(path.Join(upgradePath, upgradeFiles[i].Name()), taskItem, &size, &fileCount) + } } } - } - dropFileOrDirWithLog(path.Join(global.CONF.System.BaseDir, snapshotTmpPath), &logs, &size, &fileCount) - dropFileOrDirWithLog(path.Join(global.CONF.System.Backup, "system"), &logs, &size, &fileCount) - - dropFileOrDirWithLog(path.Join(global.CONF.System.BaseDir, rollbackPath, "app"), &logs, &size, &fileCount) - dropFileOrDirWithLog(path.Join(global.CONF.System.BaseDir, rollbackPath, "website"), &logs, &size, &fileCount) - dropFileOrDirWithLog(path.Join(global.CONF.System.BaseDir, rollbackPath, "database"), &logs, &size, &fileCount) - - dropFileOrDirWithLog(path.Join(global.CONF.System.BaseDir, oldOriginalPath), &logs, &size, &fileCount) - dropFileOrDirWithLog(path.Join(global.CONF.System.BaseDir, oldAppBackupPath), &logs, &size, &fileCount) - dropFileOrDirWithLog(path.Join(global.CONF.System.BaseDir, oldDownloadPath), &logs, &size, &fileCount) - oldUpgradePath := path.Join(global.CONF.System.BaseDir, oldUpgradePath) - oldUpgradeFiles, _ := os.ReadDir(oldUpgradePath) - if len(oldUpgradeFiles) != 0 { - for i := 0; i < len(oldUpgradeFiles); i++ { - dropFileOrDirWithLog(path.Join(oldUpgradePath, oldUpgradeFiles[i].Name()), &logs, &size, &fileCount) + dropWithTask(path.Join(global.CONF.System.BaseDir, snapshotTmpPath), taskItem, &size, &fileCount) + dropWithTask(path.Join(global.CONF.System.Backup, "system"), taskItem, &size, &fileCount) + + dropWithTask(path.Join(global.CONF.System.BaseDir, rollbackPath, "app"), taskItem, &size, &fileCount) + dropWithTask(path.Join(global.CONF.System.BaseDir, rollbackPath, "website"), taskItem, &size, &fileCount) + dropWithTask(path.Join(global.CONF.System.BaseDir, rollbackPath, "database"), taskItem, &size, &fileCount) + + dropWithTask(path.Join(global.CONF.System.BaseDir, oldOriginalPath), taskItem, &size, &fileCount) + dropWithTask(path.Join(global.CONF.System.BaseDir, oldAppBackupPath), taskItem, &size, &fileCount) + dropWithTask(path.Join(global.CONF.System.BaseDir, oldDownloadPath), taskItem, &size, &fileCount) + oldUpgradePath := path.Join(global.CONF.System.BaseDir, oldUpgradePath) + oldUpgradeFiles, _ := os.ReadDir(oldUpgradePath) + if len(oldUpgradeFiles) != 0 { + for i := 0; i < len(oldUpgradeFiles); i++ { + dropWithTask(path.Join(oldUpgradePath, oldUpgradeFiles[i].Name()), taskItem, &size, &fileCount) + } } - } - dropFileOrDirWithLog(path.Join(global.CONF.System.BaseDir, tmpUploadPath), &logs, &size, &fileCount) - dropFileOrDirWithLog(path.Join(global.CONF.System.BaseDir, uploadPath), &logs, &size, &fileCount) - dropFileOrDirWithLog(path.Join(global.CONF.System.BaseDir, downloadPath), &logs, &size, &fileCount) + dropWithTask(path.Join(global.CONF.System.BaseDir, tmpUploadPath), taskItem, &size, &fileCount) + dropWithTask(path.Join(global.CONF.System.BaseDir, uploadPath), taskItem, &size, &fileCount) + dropWithTask(path.Join(global.CONF.System.BaseDir, downloadPath), taskItem, &size, &fileCount) - logPath := path.Join(global.CONF.System.BaseDir, logPath) - logFiles, _ := os.ReadDir(logPath) - if len(logFiles) != 0 { - for i := 0; i < len(logFiles); i++ { - if logFiles[i].Name() != "1Panel.log" { - dropFileOrDirWithLog(path.Join(logPath, logFiles[i].Name()), &logs, &size, &fileCount) + logPath := path.Join(global.CONF.System.BaseDir, logPath) + logFiles, _ := os.ReadDir(logPath) + if len(logFiles) != 0 { + for i := 0; i < len(logFiles); i++ { + if logFiles[i].IsDir() { + continue + } + if logFiles[i].Name() != "1Panel.log" { + dropWithTask(path.Join(logPath, logFiles[i].Name()), taskItem, &size, &fileCount) + } } } - } - timeNow := time.Now().Format(constant.DateTimeLayout) - logs += fmt.Sprintf("\n%s: total clean: %s, total count: %d", timeNow, common.LoadSizeUnit2F(float64(size)), fileCount) + timeNow := time.Now().Format(constant.DateTimeLayout) + if fileCount != 0 { + taskItem.LogSuccessF("%s: total clean: %s, total count: %d", timeNow, common.LoadSizeUnit2F(float64(size)), fileCount) + } - _ = settingRepo.Update("LastCleanTime", timeNow) - _ = settingRepo.Update("LastCleanSize", fmt.Sprintf("%v", size)) - _ = settingRepo.Update("LastCleanData", fmt.Sprintf("%v", fileCount)) + _ = settingRepo.Update("LastCleanTime", timeNow) + _ = settingRepo.Update("LastCleanSize", fmt.Sprintf("%v", size)) + _ = settingRepo.Update("LastCleanData", fmt.Sprintf("%v", fileCount)) - return logs, nil + return nil + }, nil) + return taskItem.Execute() } func loadSnapshotTree(fileOp fileUtils.FileOp) []dto.CleanTree { @@ -695,18 +704,19 @@ func dropVolumes() { } } -func dropFileOrDirWithLog(itemPath string, log *string, size *int64, count *int) { +func dropWithTask(itemPath string, taskItem *task.Task, size *int64, count *int) { itemSize := int64(0) itemCount := 0 scanFile(itemPath, &itemSize, &itemCount) *size += itemSize *count += itemCount if err := os.RemoveAll(itemPath); err != nil { - global.LOG.Errorf("drop file %s failed, err %v", itemPath, err) - *log += fmt.Sprintf("- drop file %s failed, err: %v \n\n", itemPath, err) + taskItem.LogFailed(fmt.Sprintf("drop file %s, err %v", itemPath, err)) return } - *log += fmt.Sprintf("+ drop file %s successful!, size: %s, count: %d \n\n", itemPath, common.LoadSizeUnit2F(float64(itemSize)), itemCount) + if itemCount != 0 { + taskItem.LogSuccessF("drop file %s, size: %s, count: %d", itemPath, common.LoadSizeUnit2F(float64(itemSize)), itemCount) + } } func scanFile(pathItem string, size *int64, count *int) { diff --git a/agent/app/service/snapshot.go b/agent/app/service/snapshot.go index fce7dfd95655..3e9c44fdc984 100644 --- a/agent/app/service/snapshot.go +++ b/agent/app/service/snapshot.go @@ -30,7 +30,7 @@ type ISnapshotService interface { SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error) LoadSize(req dto.SearchWithPage) ([]dto.SnapshotFile, error) LoadSnapshotData() (dto.SnapshotData, error) - SnapshotCreate(req dto.SnapshotCreate) error + SnapshotCreate(req dto.SnapshotCreate, isCron bool) error SnapshotReCreate(id uint) error SnapshotRecover(req dto.SnapshotRecover) error SnapshotRollback(req dto.SnapshotRecover) error @@ -38,8 +38,6 @@ type ISnapshotService interface { Delete(req dto.SnapshotBatchDelete) error UpdateDescription(req dto.UpdateDescription) error - - HandleSnapshot(req dto.SnapshotCreate) error } func NewISnapshotService() ISnapshotService { @@ -87,6 +85,8 @@ func (u *SnapshotService) LoadSize(req dto.SearchWithPage) ([]dto.SnapshotFile, backupName := fmt.Sprintf("%s - %s", backup.Type, backup.Name) clientMap[uint(itemVal)] = loadSizeHelper{backupPath: strings.TrimLeft(backup.BackupPath, "/"), client: client, isOk: true, backupName: backupName} accountNames = append(accountNames, backupName) + } else { + accountNames = append(accountNames, clientMap[uint(itemVal)].backupName) } } data.DefaultDownload = clientMap[records[i].DownloadAccountID].backupName diff --git a/agent/app/service/snapshot_create.go b/agent/app/service/snapshot_create.go index 637415ef5b01..c1d9f0f7cac5 100644 --- a/agent/app/service/snapshot_create.go +++ b/agent/app/service/snapshot_create.go @@ -24,10 +24,16 @@ import ( "gorm.io/gorm" ) -func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error { +func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate, isCron bool) error { versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion")) - req.Name = fmt.Sprintf("1panel-%s-linux-%s-%s", versionItem.Value, loadOs(), time.Now().Format(constant.DateTimeSlimLayout)) + scope := "core" + if !global.IsMaster { + scope = "agent" + } + if !isCron { + req.Name = fmt.Sprintf("1panel-%s-%s-linux-%s-%s", versionItem.Value, scope, loadOs(), time.Now().Format(constant.DateTimeSlimLayout)) + } appItem, _ := json.Marshal(req.AppData) panelItem, _ := json.Marshal(req.PanelData) backupItem, _ := json.Marshal(req.BackupData) @@ -57,9 +63,16 @@ func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error { } req.ID = snap.ID - if err := u.HandleSnapshot(req); err != nil { + taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeSnapshot, req.TaskID, req.ID) + if err != nil { + global.LOG.Errorf("new task for create snapshot failed, err: %v", err) return err } + if !isCron { + go handleSnapshot(req, taskItem) + return nil + } + handleSnapshot(req, taskItem) return nil } @@ -85,101 +98,95 @@ func (u *SnapshotService) SnapshotReCreate(id uint) error { return err } req.TaskID = taskModel.ID - if err := u.HandleSnapshot(req); err != nil { - return err - } - - return nil -} - -func (u *SnapshotService) HandleSnapshot(req dto.SnapshotCreate) error { taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeSnapshot, req.TaskID, req.ID) if err != nil { global.LOG.Errorf("new task for create snapshot failed, err: %v", err) return err } + go handleSnapshot(req, taskItem) + return nil +} + +func handleSnapshot(req dto.SnapshotCreate, taskItem *task.Task) { rootDir := path.Join(global.CONF.System.BaseDir, "1panel/tmp/system", req.Name) itemHelper := snapHelper{SnapID: req.ID, Task: *taskItem, FileOp: files.NewFileOp(), Ctx: context.Background()} baseDir := path.Join(rootDir, "base") _ = os.MkdirAll(baseDir, os.ModePerm) - go func() { + taskItem.AddSubTaskWithAlias( + "SnapDBInfo", + func(t *task.Task) error { return loadDbConn(&itemHelper, rootDir, req) }, + nil, + ) + + if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapBaseInfo" { taskItem.AddSubTaskWithAlias( - "SnapDBInfo", - func(t *task.Task) error { return loadDbConn(&itemHelper, rootDir, req) }, + "SnapBaseInfo", + func(t *task.Task) error { return snapBaseData(itemHelper, baseDir) }, nil, ) - - if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapBaseInfo" { - taskItem.AddSubTaskWithAlias( - "SnapBaseInfo", - func(t *task.Task) error { return snapBaseData(itemHelper, baseDir) }, - nil, - ) - req.InterruptStep = "" - } - if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapInstallApp" { - taskItem.AddSubTaskWithAlias( - "SnapInstallApp", - func(t *task.Task) error { return snapAppImage(itemHelper, req, rootDir) }, - nil, - ) - req.InterruptStep = "" - } - if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapLocalBackup" { - taskItem.AddSubTaskWithAlias( - "SnapLocalBackup", - func(t *task.Task) error { return snapBackupData(itemHelper, req, rootDir) }, - nil, - ) - req.InterruptStep = "" - } - if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapPanelData" { - taskItem.AddSubTaskWithAlias( - "SnapPanelData", - func(t *task.Task) error { return snapPanelData(itemHelper, req, rootDir) }, - nil, - ) - req.InterruptStep = "" - } - - taskItem.AddSubTask( - "SnapCloseDBConn", + req.InterruptStep = "" + } + if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapInstallApp" { + taskItem.AddSubTaskWithAlias( + "SnapInstallApp", + func(t *task.Task) error { return snapAppImage(itemHelper, req, rootDir) }, + nil, + ) + req.InterruptStep = "" + } + if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapLocalBackup" { + taskItem.AddSubTaskWithAlias( + "SnapLocalBackup", + func(t *task.Task) error { return snapBackupData(itemHelper, req, rootDir) }, + nil, + ) + req.InterruptStep = "" + } + if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapPanelData" { + taskItem.AddSubTaskWithAlias( + "SnapPanelData", + func(t *task.Task) error { return snapPanelData(itemHelper, req, rootDir) }, + nil, + ) + req.InterruptStep = "" + } + + taskItem.AddSubTask( + "SnapCloseDBConn", + func(t *task.Task) error { + taskItem.Log("---------------------- 6 / 8 ----------------------") + common.CloseDB(itemHelper.snapAgentDB) + common.CloseDB(itemHelper.snapCoreDB) + return nil + }, + nil, + ) + if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapCompress" { + taskItem.AddSubTaskWithAlias( + "SnapCompress", + func(t *task.Task) error { return snapCompress(itemHelper, rootDir, req.Secret) }, + nil, + ) + req.InterruptStep = "" + } + if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapUpload" { + taskItem.AddSubTaskWithAlias( + "SnapUpload", func(t *task.Task) error { - taskItem.Log("---------------------- 6 / 8 ----------------------") - common.CloseDB(itemHelper.snapAgentDB) - common.CloseDB(itemHelper.snapCoreDB) - return nil + return snapUpload(itemHelper, req.SourceAccountIDs, fmt.Sprintf("%s.tar.gz", rootDir)) }, nil, ) - if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapCompress" { - taskItem.AddSubTaskWithAlias( - "SnapCompress", - func(t *task.Task) error { return snapCompress(itemHelper, rootDir, req.Secret) }, - nil, - ) - req.InterruptStep = "" - } - if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapUpload" { - taskItem.AddSubTaskWithAlias( - "SnapUpload", - func(t *task.Task) error { - return snapUpload(itemHelper, req.SourceAccountIDs, fmt.Sprintf("%s.tar.gz", rootDir)) - }, - nil, - ) - req.InterruptStep = "" - } - if err := taskItem.Execute(); err != nil { - _ = snapshotRepo.Update(req.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error(), "interrupt_step": taskItem.Task.CurrentStep}) - return - } - _ = snapshotRepo.Update(req.ID, map[string]interface{}{"status": constant.StatusSuccess, "interrupt_step": ""}) - _ = os.RemoveAll(rootDir) - }() - return nil + req.InterruptStep = "" + } + if err := taskItem.Execute(); err != nil { + _ = snapshotRepo.Update(req.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error(), "interrupt_step": taskItem.Task.CurrentStep}) + return + } + _ = snapshotRepo.Update(req.ID, map[string]interface{}{"status": constant.StatusSuccess, "interrupt_step": ""}) + _ = os.RemoveAll(rootDir) } type snapHelper struct { diff --git a/agent/app/task/task.go b/agent/app/task/task.go index f025e5554491..749387941f4f 100644 --- a/agent/app/task/task.go +++ b/agent/app/task/task.go @@ -3,13 +3,14 @@ package task import ( "context" "fmt" - "github.com/1Panel-dev/1Panel/agent/buserr" "log" "os" "path" "strconv" "time" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/repo" "github.com/1Panel-dev/1Panel/agent/constant" @@ -68,6 +69,7 @@ const ( TaskScopeRuntime = "Runtime" TaskScopeDatabase = "Database" TaskScopeCronjob = "Cronjob" + TaskScopeSystem = "System" TaskScopeAppStore = "AppStore" TaskScopeSnapshot = "Snapshot" TaskScopeContainer = "Container" @@ -234,14 +236,14 @@ func (t *Task) DeleteLogFile() { func (t *Task) LogWithStatus(msg string, err error) { if err != nil { - t.Logger.Printf(i18n.GetWithNameAndErr("FailedStatus", msg, err)) + t.Logger.Print(i18n.GetWithNameAndErr("FailedStatus", msg, err)) } else { - t.Logger.Printf(i18n.GetWithName("SuccessStatus", msg)) + t.Logger.Print(i18n.GetWithName("SuccessStatus", msg)) } } func (t *Task) Log(msg string) { - t.Logger.Printf(msg) + t.Logger.Print(msg) } func (t *Task) Logf(format string, v ...any) { diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index 36afc06997db..c71fbad5b250 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -187,6 +187,9 @@ ErrFirewallBoth: "Both firewalld and ufw services are detected on the system. To #cronjob ErrCutWebsiteLog: "{{ .name }} website log cutting failed, error {{ .err }}" CutWebsiteLogSuccess: "{{ .name }} website log cut successfully, backup path {{ .path }}" +HandleShell: "Execute script {{ .name }}" +HandleNtpSync: "System time synchronization" +HandleSystemClean: "System cache cleanup" #toolbox ErrNotExistUser: "The current user does not exist. Please modify and retry!" diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index 1e57eecbefdd..be55844a8111 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -188,6 +188,9 @@ ErrFirewallBoth: "檢測到系統同時存在 firewalld 或 ufw 服務,為避 #cronjob ErrCutWebsiteLog: "{{ .name }} 網站日誌切割失敗,錯誤 {{ .err }}" CutWebsiteLogSuccess: "{{ .name }} 網站日誌切割成功,備份路徑 {{ .path }}" +HandleShell: "執行腳本 {{ .name }}" +HandleNtpSync: "系統時間同步" +HandleSystemClean: "系統快取清理" #toolbox ErrNotExistUser: "當前使用者不存在,請修改後重試!" diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index b0e6a38ea7c9..98f9202ec1ad 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -186,6 +186,9 @@ ErrFirewallBoth: "检测到系统同时存在 firewalld 或 ufw 服务,为避 #cronjob ErrCutWebsiteLog: "{{ .name }} 网站日志切割失败,错误 {{ .err }}" CutWebsiteLogSuccess: "{{ .name }} 网站日志切割成功,备份路径 {{ .path }}" +HandleShell: "执行脚本 {{ .name }}" +HandleNtpSync: "系统时间同步" +HandleSystemClean: "系统缓存清理" #toolbox ErrNotExistUser: "当前用户不存在,请修改后重试!" diff --git a/agent/utils/cmd/cmd.go b/agent/utils/cmd/cmd.go index 49de670bb624..51ce0cf4b06c 100644 --- a/agent/utils/cmd/cmd.go +++ b/agent/utils/cmd/cmd.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/1Panel-dev/1Panel/agent/app/task" "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/constant" ) @@ -137,6 +138,39 @@ func ExecShell(outPath string, timeout time.Duration, name string, arg ...string return nil } +type CustomWriter struct { + taskItem *task.Task +} + +func (cw *CustomWriter) Write(p []byte) (n int, err error) { + cw.taskItem.Log(string(p)) + return len(p), nil +} +func ExecShellWithTask(taskItem *task.Task, timeout time.Duration, name string, arg ...string) error { + customWriter := &CustomWriter{taskItem: taskItem} + cmd := exec.Command(name, arg...) + cmd.Stdout = customWriter + cmd.Stderr = customWriter + if err := cmd.Start(); err != nil { + return err + } + done := make(chan error, 1) + go func() { + done <- cmd.Wait() + }() + after := time.After(timeout) + select { + case <-after: + _ = cmd.Process.Kill() + return buserr.New(constant.ErrCmdTimeout) + case err := <-done: + if err != nil { + return err + } + } + return nil +} + func Execf(cmdStr string, a ...interface{}) (string, error) { cmd := exec.Command("bash", "-c", fmt.Sprintf(cmdStr, a...)) var stdout, stderr bytes.Buffer diff --git a/frontend/src/api/interface/cronjob.ts b/frontend/src/api/interface/cronjob.ts index f6914a2f3a1e..a8681de388f4 100644 --- a/frontend/src/api/interface/cronjob.ts +++ b/frontend/src/api/interface/cronjob.ts @@ -105,6 +105,7 @@ export namespace Cronjob { } export interface Record { id: number; + taskID: string; file: string; startTime: string; records: string; diff --git a/frontend/src/components/log-file/index.vue b/frontend/src/components/log-file/index.vue index ea326e530dfd..9715f634ef23 100644 --- a/frontend/src/components/log-file/index.vue +++ b/frontend/src/components/log-file/index.vue @@ -4,7 +4,13 @@ {{ $t('commons.button.watch') }} - + {{ $t('file.download') }} @@ -67,6 +73,10 @@ const props = defineProps({ type: Boolean, default: true, }, + showDownload: { + type: Boolean, + default: true, + }, }); const stopSignals = [ 'docker-compose up failed!', diff --git a/frontend/src/components/task-log/log-without-dialog.vue b/frontend/src/components/task-log/log-without-dialog.vue new file mode 100644 index 000000000000..a71cdd4b0690 --- /dev/null +++ b/frontend/src/components/task-log/log-without-dialog.vue @@ -0,0 +1,22 @@ + + diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index a293e2c2fba6..af5a04113b26 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1658,6 +1658,8 @@ const message = { 'Backup files not in the current backup list, please try downloading from the file directory and importing for backup.', snapshot: 'Snapshot', + noAppData: 'No system applications available for selection', + noBackupData: 'No backup data available for selection', stepBaseData: 'Base Data', stepAppData: 'System Application', stepPanelData: 'System Data', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 822136ec2b25..f1e0b344f488 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1469,6 +1469,8 @@ const message = { backupJump: '未在當前備份列表中的備份檔案,請嘗試從檔案目錄中下載後導入備份。', snapshot: '快照', + noAppData: '暫無可選擇系統應用', + noBackupData: '暫無可選擇備份數據', stepBaseData: '基礎數據', stepAppData: '系統應用', stepPanelData: '系統數據', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 468c3ccbd95a..c308c1679441 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1470,6 +1470,8 @@ const message = { backupJump: '未在当前备份列表中的备份文件,请尝试从文件目录中下载后导入备份。', snapshot: '快照', + noAppData: '暂无可选择系统应用', + noBackupData: '暂无可选择备份数据', stepBaseData: '基础数据', stepAppData: '系统应用', stepPanelData: '系统数据', diff --git a/frontend/src/views/cronjob/operate/index.vue b/frontend/src/views/cronjob/operate/index.vue index 5753c0deb9da..47adf6ad9598 100644 --- a/frontend/src/views/cronjob/operate/index.vue +++ b/frontend/src/views/cronjob/operate/index.vue @@ -368,7 +368,7 @@ - + {{ $t('file.dir') }} {{ $t('file.file') }} diff --git a/frontend/src/views/cronjob/record/index.vue b/frontend/src/views/cronjob/record/index.vue index 8aa92fe275d9..0bd95f51c406 100644 --- a/frontend/src/views/cronjob/record/index.vue +++ b/frontend/src/views/cronjob/record/index.vue @@ -172,6 +172,14 @@ > + + + @@ -221,6 +229,7 @@ import { MsgSuccess } from '@/utils/message'; import { listDbItems } from '@/api/modules/database'; import { ListAppInstalled } from '@/api/modules/app'; import { shortcuts } from '@/utils/shortcuts'; +import TaskLog from '@/components/task-log/log-without-dialog.vue'; const loading = ref(); const refresh = ref(false); diff --git a/frontend/src/views/setting/snapshot/create/index.vue b/frontend/src/views/setting/snapshot/create/index.vue index c880f2633853..c085d7f6aee6 100644 --- a/frontend/src/views/setting/snapshot/create/index.vue +++ b/frontend/src/views/setting/snapshot/create/index.vue @@ -53,31 +53,36 @@ - - - - +
+ {{ $t('setting.noAppData') }} +
+
+ + + + +
- - - +
+ {{ $t('setting.noBackupData') }} +
+
+ + + +
@@ -239,16 +249,20 @@ const beforeLeave = async (stepItem: any) => { return false; } case 'appData': - let appChecks = appRef.value.getCheckedNodes(); - loadCheckForSubmit(appChecks, form.appData); + if (form.appData && form.appData.length !== 0) { + let appChecks = appRef.value.getCheckedNodes(); + loadCheckForSubmit(appChecks, form.appData); + } return true; case 'panelData': let panelChecks = panelRef.value.getCheckedNodes(); loadCheckForSubmit(panelChecks, form.panelData); return true; case 'backupData': - let backupChecks = backupRef.value.getCheckedNodes(); - loadCheckForSubmit(backupChecks, form.backupData); + if (form.backupData && form.backupData.length !== 0) { + let backupChecks = backupRef.value.getCheckedNodes(); + loadCheckForSubmit(backupChecks, form.backupData); + } return true; } };