diff --git a/agent/app/dto/cronjob.go b/agent/app/dto/cronjob.go index 8363e45ed..662921d67 100644 --- a/agent/app/dto/cronjob.go +++ b/agent/app/dto/cronjob.go @@ -45,6 +45,7 @@ type CronjobOperate struct { RetainCopies int `json:"retainCopies" validate:"number,min=1"` RetryTimes int `json:"retryTimes" validate:"number,min=0"` Timeout uint `json:"timeout" validate:"number,min=1"` + IgnoreErr bool `json:"ignoreErr"` Secret string `json:"secret"` AlertCount uint `json:"alertCount"` @@ -105,6 +106,7 @@ type CronjobInfo struct { RetainCopies int `json:"retainCopies"` RetryTimes int `json:"retryTimes"` Timeout uint `json:"timeout"` + IgnoreErr bool `json:"ignoreErr"` SnapshotRule SnapshotRule `json:"snapshotRule"` SourceAccounts []string `json:"sourceAccounts"` diff --git a/agent/app/model/cronjob.go b/agent/app/model/cronjob.go index 3c56c01b0..d743e903c 100644 --- a/agent/app/model/cronjob.go +++ b/agent/app/model/cronjob.go @@ -36,6 +36,7 @@ type Cronjob struct { DownloadAccountID uint `json:"downloadAccountID"` RetryTimes uint `json:"retryTimes"` Timeout uint `json:"timeout"` + IgnoreErr bool `json:"ignoreErr"` RetainCopies uint64 `json:"retainCopies"` Status string `json:"status"` diff --git a/agent/app/service/cronjob.go b/agent/app/service/cronjob.go index aa7e89abc..3d8eee372 100644 --- a/agent/app/service/cronjob.go +++ b/agent/app/service/cronjob.go @@ -439,6 +439,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error { upMap["retain_copies"] = req.RetainCopies upMap["retry_times"] = req.RetryTimes upMap["timeout"] = req.Timeout + upMap["ignore_err"] = req.IgnoreErr upMap["secret"] = req.Secret err = cronjobRepo.Update(id, upMap) if err != nil { diff --git a/agent/app/service/cronjob_backup.go b/agent/app/service/cronjob_backup.go index f1a3f6959..78730e8aa 100644 --- a/agent/app/service/cronjob_backup.go +++ b/agent/app/service/cronjob_backup.go @@ -48,6 +48,7 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, t return err } for _, app := range apps { + retry := 0 taskItem.AddSubTaskWithOps(task.GetTaskName(app.Name, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error { var record model.BackupRecord record.From = "cronjob" @@ -59,11 +60,22 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, t backupDir := path.Join(global.Dir.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 := doAppBackup(&app, task, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil { - return err + if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr { + retry++ + return err + } else { + task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error())) + return nil + } } downloadPath, err := u.uploadCronjobBackFile(cronjob, task, accountMap, path.Join(backupDir, record.FileName)) if err != nil { - return err + if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr { + retry++ + return err + } + task.Log(i18n.GetMsgWithDetail("IgnoreUploadErr", err.Error())) + return nil } record.FileDir = path.Dir(downloadPath) if err := backupRepo.CreateRecord(&record); err != nil { @@ -87,6 +99,7 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim return err } for _, web := range webs { + retry := 0 taskItem.AddSubTaskWithOps(task.GetTaskName(web.Alias, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error { var record model.BackupRecord record.From = "cronjob" @@ -99,12 +112,23 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", web.Alias, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)) if err := doWebsiteBackup(&web, taskItem, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil { - return err + if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr { + retry++ + return err + } else { + task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error())) + return nil + } } downloadPath, err := u.uploadCronjobBackFile(cronjob, task, accountMap, path.Join(backupDir, record.FileName)) if err != nil { - return err + if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr { + retry++ + return err + } + task.Log(i18n.GetMsgWithDetail("IgnoreUploadErr", err.Error())) + return nil } record.FileDir = path.Dir(downloadPath) if err := backupRepo.CreateRecord(&record); err != nil { @@ -128,6 +152,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti return err } for _, dbInfo := range dbs { + retry := 0 itemName := fmt.Sprintf("%s[%s] - %s", dbInfo.Database, dbInfo.DBType, dbInfo.Name) taskItem.AddSubTaskWithOps(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error { var record model.BackupRecord @@ -142,17 +167,34 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti 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 := doMysqlBackup(dbInfo, backupDir, record.FileName); err != nil { - return err + if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr { + retry++ + return err + } else { + task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error())) + return nil + } } } else { if err := doPostgresqlgBackup(dbInfo, backupDir, record.FileName); err != nil { - return err + if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr { + retry++ + return err + } else { + task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error())) + return nil + } } } downloadPath, err := u.uploadCronjobBackFile(cronjob, task, accountMap, path.Join(backupDir, record.FileName)) if err != nil { - return err + if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr { + retry++ + return err + } + task.Log(i18n.GetMsgWithDetail("IgnoreUploadErr", err.Error())) + return nil } record.FileDir = path.Dir(downloadPath) if err := backupRepo.CreateRecord(&record); err != nil { @@ -280,6 +322,7 @@ func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, jobRecord model.J AppData: itemData.AppData, PanelData: itemData.PanelData, BackupData: itemData.BackupData, + WithDockerConf: true, WithMonitorData: true, WithLoginLog: true, WithOperationLog: true, diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index a77885a13..ce900fbb6 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -205,6 +205,8 @@ SystemLog: 'System Log' CutWebsiteLog: 'Rotate Website Log' FileOrDir: 'Directory / File' UploadFile: 'Uploading backup file {{ .file }} to {{ .backup }}' +IgnoreBackupErr: 'Backup failed, error: {{ .detail }}, ignoring this error...' +IgnoreUploadErr: 'Upload failed, error: {{ .detail }}, ignoring this error...' #toolbox ErrNotExistUser: 'The current user does not exist, please modify and try again!' diff --git a/agent/i18n/lang/ja.yaml b/agent/i18n/lang/ja.yaml index 7184a75ba..0d95e7a90 100644 --- a/agent/i18n/lang/ja.yaml +++ b/agent/i18n/lang/ja.yaml @@ -205,6 +205,8 @@ SystemLog: 'システムログ' CutWebsiteLog: 'ウェブサイトログのローテーション' FileOrDir: 'ディレクトリ / ファイル' UploadFile: 'バックアップファイル {{ .file }} を {{ .backup }} にアップロード中' +IgnoreBackupErr: 'バックアップ失敗、エラー:{{ .detail }}、このエラーを無視します...' +IgnoreUploadErr: 'アップロード失敗、エラー:{{ .detail }}、このエラーを無視します...' #toolbox ErrNotExistUser: '現在のユーザーは存在しません。変更してもう一度お試しください。' diff --git a/agent/i18n/lang/ko.yaml b/agent/i18n/lang/ko.yaml index 3382ecd45..f5fda0462 100644 --- a/agent/i18n/lang/ko.yaml +++ b/agent/i18n/lang/ko.yaml @@ -205,6 +205,8 @@ SystemLog: '시스템 로그' CutWebsiteLog: '웹사이트 로그 회전' FileOrDir: '디렉터리 / 파일' UploadFile: '백업 파일 {{ .file }} 을(를) {{ .backup }}(으)로 업로드 중' +IgnoreBackupErr: 'Sandaran gagal, ralat: {{ .detail }}, abaikan ralat ini...' +IgnoreUploadErr: 'Muat naik gagal, ralat: {{ .detail }}, abaikan ralat ini...' #도구상자 ErrNotExistUser: '현재 사용자가 존재하지 않습니다. 수정한 후 다시 시도하세요!' diff --git a/agent/i18n/lang/ms.yaml b/agent/i18n/lang/ms.yaml index 61eb9f6bd..8528016ec 100644 --- a/agent/i18n/lang/ms.yaml +++ b/agent/i18n/lang/ms.yaml @@ -204,6 +204,8 @@ SystemLog: 'Log Sistem' CutWebsiteLog: 'Putar Log Laman Web' FileOrDir: 'Direktori / Fail' UploadFile: 'Muat naik fail sandaran {{ .file }} ke {{ .backup }}' +IgnoreBackupErr: 'Sandaran gagal, ralat: {{ .detail }}, abaikan ralat ini...' +IgnoreUploadErr: 'Muat naik gagal, ralat: {{ .detail }}, abaikan ralat ini...' #kotak alat ErrNotExistUser: 'Pengguna semasa tidak wujud, sila ubah suai dan cuba lagi!' diff --git a/agent/i18n/lang/pt-BR.yaml b/agent/i18n/lang/pt-BR.yaml index 89590725c..dcc60af73 100644 --- a/agent/i18n/lang/pt-BR.yaml +++ b/agent/i18n/lang/pt-BR.yaml @@ -205,6 +205,8 @@ SystemLog: 'Log do Sistema' CutWebsiteLog: 'Rotacionar Log do Website' FileOrDir: 'Diretório / Arquivo' UploadFile: 'Enviando arquivo de backup {{ .file }} para {{ .backup }}' +IgnoreBackupErr: 'Backup falhou, erro: {{ .detail }}, ignorando este erro...' +IgnoreUploadErr: 'Upload falhou, erro: {{ .detail }}, ignorando este erro...' #caixa de ferramentas ErrNotExistUser: 'O usuário atual não existe, modifique e tente novamente!' diff --git a/agent/i18n/lang/ru.yaml b/agent/i18n/lang/ru.yaml index ac9eb51d0..74222e168 100644 --- a/agent/i18n/lang/ru.yaml +++ b/agent/i18n/lang/ru.yaml @@ -205,6 +205,8 @@ SystemLog: 'Системный лог' CutWebsiteLog: 'Ротация логов сайта' FileOrDir: 'Каталог / Файл' UploadFile: 'Загрузка файла резервной копии {{ .file }} в {{ .backup }}' +IgnoreBackupErr: 'Ошибка резервного копирования: {{ .detail }}, игнорируем эту ошибку...' +IgnoreUploadErr: 'Ошибка загрузки: {{ .detail }}, игнорируем эту ошибку...' #ящик для инструментов ErrNotExistUser: 'Текущий пользователь не существует, измените его и повторите попытку!' diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index 4152879c4..1ad1319cf 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -204,6 +204,8 @@ SystemLog: '系統日誌' CutWebsiteLog: '切割網站日誌' FileOrDir: '目錄 / 檔案' UploadFile: '上傳備份文件 {{ .file }} 到 {{ .backup }}' +IgnoreBackupErr: '備份失敗,錯誤:{{ .detail }},忽略本次錯誤...' +IgnoreUploadErr: '上傳失敗,錯誤:{{ .detail }},忽略本次錯誤...' #toolbox ErrNotExistUser: '目前使用者不存在,請修改後重試!' diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index cd8221df3..0dd9f2863 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -204,6 +204,8 @@ SystemLog: "系统日志" CutWebsiteLog: "切割网站日志" FileOrDir: "目录 / 文件" UploadFile: "上传备份文件 {{ .file }} 到 {{ .backup }}" +IgnoreBackupErr: "备份失败,错误:{{ .detail }},忽略本次错误..." +IgnoreUploadErr: "上传失败,错误:{{ .detail }},忽略本次错误..." #toolbox ErrNotExistUser: "当前用户不存在,请修改后重试!" diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index b0ebd88e9..4dc0abc41 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -322,7 +322,7 @@ var UpdateRuntime = &gormigrate.Migration{ } var AddSnapshotRule = &gormigrate.Migration{ - ID: "20250627-add-snapshot-rule", + ID: "20250703-add-snapshot-rule", Migrate: func(tx *gorm.DB) error { return tx.AutoMigrate( &model.Cronjob{}, diff --git a/frontend/src/api/interface/cronjob.ts b/frontend/src/api/interface/cronjob.ts index 50ecc6ac2..ea2b784ef 100644 --- a/frontend/src/api/interface/cronjob.ts +++ b/frontend/src/api/interface/cronjob.ts @@ -45,6 +45,7 @@ export namespace Cronjob { sourceAccountItems: Array; retainCopies: number; + ignoreErr: boolean; retryTimes: number; timeout: number; timeoutItem: number; @@ -91,6 +92,7 @@ export namespace Cronjob { retainCopies: number; retryTimes: number; timeout: number; + ignoreErr: boolean; secret: string; alertCount: number; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index f04b19b43..b2d4f2a30 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1035,6 +1035,8 @@ const message = { retainCopies: 'Retain records', retryTimes: 'Retry Attempts', timeout: 'Timeout', + ignoreErr: 'Ignore errors', + ignoreErrHelper: 'Ignore errors during backup to ensure all backup tasks complete', retryTimesHelper: '0 means no retry after failure', retainCopiesHelper: 'Number of copies to retain for execution records and logs', retainCopiesHelper1: 'Number of copies to retain for backup files', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index e8dd78691..faca850f7 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -1004,6 +1004,8 @@ const message = { retainCopies: '記録を保持します', retryTimes: 'リトライ回数', timeout: 'タイムアウト', + ignoreErr: 'エラーを無視', + ignoreErrHelper: 'バックアップ中のエラーを無視し、全てのバックアップタスクを確実に実行します', retryTimesHelper: '0は失敗後リトライしないことを意味します', retainCopiesHelper: '実行記録とログのために保持するコピーの数', retainCopiesHelper1: 'バックアップファイル用に保持するコピーの数', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 094861c84..a85380066 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -991,6 +991,8 @@ const message = { retainCopies: '기록 보관', retryTimes: '재시도 횟수', timeout: '타임아웃', + ignoreErr: '오류 무시', + ignoreErrHelper: '백업 과정에서 발생하는 오류를 무시하여 모든 백업 작업이 실행되도록 합니다', retryTimesHelper: '0은 실패 후 재시도 안 함을 의미합니다', retainCopiesHelper: '실행 기록과 로그에 대해 보관할 복사본 수', retainCopiesHelper1: '백업 파일에 대해 보관할 복사본 수', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index ce842c49a..0f6d1e453 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -1025,6 +1025,8 @@ const message = { retainCopies: 'Simpan salinan', retryTimes: 'Bilangan Cubaan Semula', timeout: 'Masa Tamat', + ignoreErr: 'Abaikan ralat', + ignoreErrHelper: 'Abaikan ralat semasa sandaran untuk memastikan semua tugas sandaran dilaksanakan', retryTimesHelper: '0 bermaksud tiada cubaan semula selepas gagal', retainCopiesHelper: 'Bilangan salinan untuk menyimpan rekod pelaksanaan dan log', retainCopiesHelper1: 'Bilangan salinan untuk menyimpan fail sandaran', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 74e611292..d8cbae567 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -1021,6 +1021,8 @@ const message = { retainCopies: 'Manter cópias', retryTimes: 'Tentativas de Repetição', timeout: 'Tempo Limite', + ignoreErr: 'Ignorar erros', + ignoreErrHelper: 'Ignorar erros durante o backup para garantir a execução de todas as tarefas de backup', retryTimesHelper: '0 significa não repetir após falha', retainCopiesHelper: 'Número de cópias a serem mantidas para registros de execução e logs', retainCopiesHelper1: 'Número de cópias a serem mantidas para arquivos de backup', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 10dfd3de6..274741e3e 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -1017,6 +1017,9 @@ const message = { retainCopies: 'Сохранять записи', retryTimes: 'Количество повторов', timeout: 'Таймаут', + ignoreErr: 'Игнорировать ошибки', + ignoreErrHelper: + 'Игнорировать ошибки во время резервного копирования для выполнения всех задач резервного копирования', retryTimesHelper: '0 означает отсутствие повторов после сбоя', retainCopiesHelper: 'Количество копий для сохранения записей выполнения и логов', retainCopiesHelper1: 'Количество копий для сохранения файлов резервных копий', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 199d1a729..facc2f8dc 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -983,6 +983,8 @@ const message = { retainCopies: '保留份數', retryTimes: '失敗重試次數', timeout: '逾時時間', + ignoreErr: '忽略錯誤', + ignoreErrHelper: '忽略備份過程中出現的錯誤,保證所有備份任務執行', retryTimesHelper: '為0表示失敗後不重試', retainCopiesHelper: '執行記錄及日誌保留份数', retainCopiesHelper1: '備份文件保留份数', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 2f969e351..c3d6d2116 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -982,6 +982,8 @@ const message = { retainCopies: '保留份数', retryTimes: '失败重试次数', timeout: '超时时间', + ignoreErr: '忽略错误', + ignoreErrHelper: '忽略备份过程中出现的错误,保证所有备份任务执行', retryTimesHelper: '为 0 表示失败后不重试', retainCopiesHelper: '执行记录及日志保留份数', retainCopiesHelper1: '备份文件保留份数', diff --git a/frontend/src/views/cronjob/cronjob/operate/index.vue b/frontend/src/views/cronjob/cronjob/operate/index.vue index 647aac979..10ee2b67a 100644 --- a/frontend/src/views/cronjob/cronjob/operate/index.vue +++ b/frontend/src/views/cronjob/cronjob/operate/index.vue @@ -644,6 +644,12 @@ + + + + {{ $t('cronjob.ignoreErrHelper') }} + + @@ -776,6 +782,7 @@ const form = reactive({ dbNameList: [], retainCopies: 7, + ignoreErr: false, retryTimes: 3, timeout: 3600, timeoutItem: 3600, @@ -863,6 +870,7 @@ const search = async () => { } } + form.ignoreErr = res.data.ignoreErr; form.retainCopies = res.data.retainCopies; form.retryTimes = res.data.retryTimes; form.timeout = res.data.timeout; @@ -1287,6 +1295,10 @@ function hasExclusionRules() { ); } +function hasIgnore() { + return form.type === 'app' || form.type === 'website' || form.type === 'database'; +} + function hasScript() { return form.type === 'shell'; }