From 3f47a6e70183aa8bb9a50f87efc7c78689cef239 Mon Sep 17 00:00:00 2001 From: ssongliu <73214554+ssongliu@users.noreply.github.com> Date: Mon, 24 Nov 2025 15:57:18 +0800 Subject: [PATCH] feat: Support multiple URL access for cronjob (#11050) Refs #10279 --- agent/app/service/cronjob_helper.go | 15 +++++--- agent/i18n/lang/en.yaml | 1 + agent/i18n/lang/es-ES.yaml | 1 + agent/i18n/lang/ja.yaml | 1 + agent/i18n/lang/ko.yaml | 1 + agent/i18n/lang/ms.yaml | 1 + agent/i18n/lang/pt-BR.yaml | 1 + agent/i18n/lang/ru.yaml | 1 + agent/i18n/lang/tr.yaml | 1 + agent/i18n/lang/zh-Hant.yaml | 1 + agent/i18n/lang/zh.yaml | 1 + frontend/src/api/interface/cronjob.ts | 1 + frontend/src/lang/modules/en.ts | 1 + frontend/src/lang/modules/es-es.ts | 1 + frontend/src/lang/modules/ja.ts | 1 + frontend/src/lang/modules/ko.ts | 1 + frontend/src/lang/modules/ms.ts | 1 + frontend/src/lang/modules/pt-br.ts | 1 + frontend/src/lang/modules/ru.ts | 1 + frontend/src/lang/modules/tr.ts | 1 + frontend/src/lang/modules/zh-Hant.ts | 1 + frontend/src/lang/modules/zh.ts | 1 + .../views/cronjob/cronjob/operate/index.vue | 35 +++++++++++++++++-- 23 files changed, 64 insertions(+), 7 deletions(-) diff --git a/agent/app/service/cronjob_helper.go b/agent/app/service/cronjob_helper.go index f7e58b757..054308ad7 100644 --- a/agent/app/service/cronjob_helper.go +++ b/agent/app/service/cronjob_helper.go @@ -195,10 +195,17 @@ func (u *CronjobService) handleShell(cronjob model.Cronjob, taskItem *task.Task) } func (u *CronjobService) handleCurl(cronjob model.Cronjob, taskItem *task.Task) { - taskItem.AddSubTaskWithOps(i18n.GetWithName("HandleShell", cronjob.Name), func(t *task.Task) error { - cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem)) - return cmdMgr.Run("curl", cronjob.URL) - }, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second) + urls := strings.Split(cronjob.URL, ",") + for _, url := range urls { + if len(strings.TrimSpace(url)) == 0 { + continue + } + taskItem.AddSubTaskWithOps(i18n.GetWithName("HandleCurl", url), func(t *task.Task) error { + taskItem.LogStart(i18n.GetWithName("HandleCurl", url)) + cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem)) + return cmdMgr.Run("curl", url) + }, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second) + } } func (u *CronjobService) handleNtpSync(cronjob model.Cronjob, taskItem *task.Task) { diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index 6134ab316..abeb17128 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -241,6 +241,7 @@ ErrUserFindErr: 'User {{ .name }} search failed {{ .err }}' #cronjob CutWebsiteLogSuccess: '{{ .name }} website log cut successfully, backup path {{ .path }}' HandleShell: 'Execute script {{ .name }}' +HandleCurl: "Access URL {{ .name }}" HandleNtpSync: 'System time synchronization' HandleSystemClean: 'System cache cleanup' SystemLog: 'System Log' diff --git a/agent/i18n/lang/es-ES.yaml b/agent/i18n/lang/es-ES.yaml index 5fc5e3bb0..e64242d8a 100644 --- a/agent/i18n/lang/es-ES.yaml +++ b/agent/i18n/lang/es-ES.yaml @@ -240,6 +240,7 @@ ErrUserFindErr: 'Búsqueda del usuario {{ .name }} fallida {{ .err }}' #cronjob CutWebsiteLogSuccess: 'Log del sitio web {{ .name }} cortado con éxito, ruta de respaldo {{ .path }}' HandleShell: 'Ejecutar script {{ .name }}' +HandleCurl: "URL {{ .name }} にアクセス" HandleNtpSync: 'Sincronizar hora del sistema' HandleSystemClean: 'Limpiar caché del sistema' SystemLog: 'Log del sistema' diff --git a/agent/i18n/lang/ja.yaml b/agent/i18n/lang/ja.yaml index 13c93c797..f48f4aadb 100644 --- a/agent/i18n/lang/ja.yaml +++ b/agent/i18n/lang/ja.yaml @@ -240,6 +240,7 @@ ErrUserFindErr: 'ユーザー {{ .name }} の検索に失敗しました {{ .err #cronjob CutWebsiteLogSuccess: '{{ .name }} ウェブサイトのログが正常にカットされました。バックアップ パス {{ .path }}' HandleShell: 'スクリプト {{ .name }} を実行します' +HandleCurl: "URL {{ .name }} にアクセス" HandleNtpSync: 'システム時刻の同期' HandleSystemClean: 'システム キャッシュのクリーンアップ' SystemLog: 'システムログ' diff --git a/agent/i18n/lang/ko.yaml b/agent/i18n/lang/ko.yaml index fbeb78d51..bef83a69b 100644 --- a/agent/i18n/lang/ko.yaml +++ b/agent/i18n/lang/ko.yaml @@ -241,6 +241,7 @@ ErrUserFindErr: '사용자 {{ .name }} 검색에 실패했습니다 {{ .err }}' #크론잡 CutWebsiteLogSuccess: '{{ .name }} 웹사이트 로그가 성공적으로 잘렸습니다. 백업 경로 {{ .path }}' HandleShell: '스크립트 {{ .name }} 실행' +HandleCurl: "URL {{ .name }} 접근" HandleNtpSync: '시스템 시간 동기화' HandleSystemClean: '시스템 캐시 정리' SystemLog: '시스템 로그' diff --git a/agent/i18n/lang/ms.yaml b/agent/i18n/lang/ms.yaml index 130f57b0a..54e5cde72 100644 --- a/agent/i18n/lang/ms.yaml +++ b/agent/i18n/lang/ms.yaml @@ -241,6 +241,7 @@ ErrUserFindErr: 'Pengguna {{ .name }} carian gagal {{ .err }}' #cronjob CutWebsiteLogSuccess: '{{ .name }} log tapak web berjaya dipotong, laluan sandaran {{ .path }}' HandleShell: 'Laksanakan skrip {{ .name }}' +HandleCurl: "Akses URL {{ .name }}" HandleNtpSync: 'Penyegerakan masa sistem' HandleSystemClean: 'Pembersihan cache sistem' SystemLog: 'Log Sistem' diff --git a/agent/i18n/lang/pt-BR.yaml b/agent/i18n/lang/pt-BR.yaml index 9ed3477b2..9d7440f6b 100644 --- a/agent/i18n/lang/pt-BR.yaml +++ b/agent/i18n/lang/pt-BR.yaml @@ -241,6 +241,7 @@ ErrUserFindErr: 'Falha na pesquisa do usuário {{ .name }} {{ .err }}' #cronjob CutWebsiteLogSuccess: '{{ .name }} registro do site cortado com sucesso, caminho de backup {{ .path }}' HandleShell: 'Executar script {{ .name }}' +HandleCurl: "Acessar URL {{ .name }}" HandleNtpSync: 'Sincronização de hora do sistema' HandleSystemClean: 'Limpeza de cache do sistema' SystemLog: 'Log do Sistema' diff --git a/agent/i18n/lang/ru.yaml b/agent/i18n/lang/ru.yaml index 160f72be8..17cb82165 100644 --- a/agent/i18n/lang/ru.yaml +++ b/agent/i18n/lang/ru.yaml @@ -241,6 +241,7 @@ ErrUserFindErr: 'Поиск пользователя {{ .name }} не удалс #cronjob CutWebsiteLogSuccess: 'Журнал веб-сайта {{ .name }} успешно вырезан, путь к резервной копии {{ .path }}' HandleShell: 'Выполнить скрипт {{ .name }}' +HandleCurl: "Доступ к URL {{ .name }}" HandleNtpSync: 'Синхронизация системного времени' HandleSystemClean: 'Очистка системного кэша' SystemLog: 'Системный лог' diff --git a/agent/i18n/lang/tr.yaml b/agent/i18n/lang/tr.yaml index 8f110a8b1..2ad96ba3c 100644 --- a/agent/i18n/lang/tr.yaml +++ b/agent/i18n/lang/tr.yaml @@ -242,6 +242,7 @@ ErrUserFindErr: 'Kullanıcı {{ .name }} arama başarısız {{ .err }}' #cronjob CutWebsiteLogSuccess: '{{ .name }} web sitesi günlüğü başarıyla kesildi, yedekleme yolu {{ .path }}' HandleShell: 'Betik {{ .name }} yürüt' +HandleCurl: "URL'ye Eriş {{ .name }}" HandleNtpSync: 'Sistem zaman senkronizasyonu' HandleSystemClean: 'Sistem önbellek temizliği' SystemLog: 'Sistem Günlüğü' diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index fcf5536ed..fc811bd05 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -240,6 +240,7 @@ ErrUserFindErr: '使用者{{ .name }} 尋找失敗{{ .err }}' #cronjob CutWebsiteLogSuccess: '{{ .name }} 網站日誌切割成功,備份路徑{{ .path }}' HandleShell: '執行腳本{{ .name }}' +HandleCurl: "存取 URL {{ .name }}" HandleNtpSync: '系統時間同步' HandleSystemClean: '系統快取清理' SystemLog: '系統日誌' diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index 6ca42badc..df7835f79 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -241,6 +241,7 @@ ErrUserFindErr: "用户 {{ .name }} 查找失败 {{ .err }}" #cronjob CutWebsiteLogSuccess: "{{ .name }} 网站日志切割成功,备份路径 {{ .path }}" HandleShell: "执行脚本 {{ .name }}" +HandleCurl: "访问 URL {{ .name }}" HandleNtpSync: "系统时间同步" HandleSystemClean: "系统缓存清理" SystemLog: "系统日志" diff --git a/frontend/src/api/interface/cronjob.ts b/frontend/src/api/interface/cronjob.ts index b18ff2b0c..32016d308 100644 --- a/frontend/src/api/interface/cronjob.ts +++ b/frontend/src/api/interface/cronjob.ts @@ -34,6 +34,7 @@ export namespace Cronjob { dbType: string; dbName: string; url: string; + urlItems: Array; isDir: boolean; files: Array; sourceDir: string; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 12f01a0e2..abdd46002 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1120,6 +1120,7 @@ const message = { default_download_path: 'Default download link', saveLocal: 'Retain local backups (the same as the number of cloud storage copies)', url: 'URL Address', + urlHelper: 'Please enter a valid URL address', targetHelper: 'Backup accounts are maintained in panel settings.', withImageHelper: 'Backup app store images, but this will increase the snapshot file size.', ignoreApp: 'Exclude apps', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index e8efe931d..a85811e4d 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -1122,6 +1122,7 @@ const message = { default_download_path: 'Enlace de descarga predeterminado', saveLocal: 'Retener respaldos locales (igual al número de copias en la nube)', url: 'Dirección URL', + urlHelper: 'Por favor ingrese una dirección URL válida', targetHelper: 'Las cuentas de respaldo se gestionan en los ajustes del panel.', withImageHelper: 'Respalda imágenes de la tienda de aplicaciones, pero esto aumentará el tamaño del archivo de la instantánea.', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 4a840ce1c..98f023159 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -1090,6 +1090,7 @@ const message = { default_download_path: 'デフォルトのダウンロードリンク', saveLocal: 'ローカルバックアップを保持します(クラウドストレージコピーの数と同じ)', url: 'URLアドレス', + urlHelper: '正しいURLアドレスを入力してください', targetHelper: 'バックアップアカウントは、パネル設定で維持されます。', withImageHelper: 'アプリストアのイメージをバックアップしますが、スナップショットファイルのサイズが大きくなります。', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index ba93e5add..928a3a44f 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -1077,6 +1077,7 @@ const message = { default_download_path: '기본 다운로드 링크', saveLocal: '로컬 백업 보관 (클라우드 저장소 복사본 수와 동일)', url: 'URL 주소', + urlHelper: '올바른 URL 주소를 입력해 주세요', targetHelper: '백업 계정은 패널 설정에서 관리됩니다.', withImageHelper: '앱 스토어 이미지를 백업하지만 스냅샷 파일 크기가 증가합니다.', ignoreApp: '앱 제외', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 0d8e9b651..3ec1e9851 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -1112,6 +1112,7 @@ const message = { default_download_path: 'Pautan muat turun lalai', saveLocal: 'Simpan sandaran tempatan (sama seperti bilangan salinan storan awan)', url: 'Alamat URL', + urlHelper: 'Sila masukkan alamat URL yang sah', targetHelper: 'Akaun sandaran diselenggara dalam tetapan panel.', withImageHelper: 'Sandarkan imej kedai aplikasi, tetapi ini akan meningkatkan saiz fail snapshot.', ignoreApp: 'Kecualikan aplikasi', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 351138a21..eb4b7e3e0 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -1110,6 +1110,7 @@ const message = { default_download_path: 'Link de download padrão', saveLocal: 'Manter backups locais (o mesmo número de cópias na nuvem)', url: 'Endereço URL', + urlHelper: 'Por favor insira um endereço URL válido', targetHelper: 'As contas de backup são mantidas nas configurações do painel.', withImageHelper: 'Fazer backup das imagens da loja de aplicativos, mas isso aumentará o tamanho do arquivo de snapshot.', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 0f1ff64c8..43fa0a08c 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -1106,6 +1106,7 @@ const message = { default_download_path: 'Ссылка для скачивания по умолчанию', saveLocal: 'Сохранять локальные резервные копии (столько же, сколько копий в облачном хранилище)', url: 'URL-адрес', + urlHelper: 'Пожалуйста, введите действительный URL-адрес', targetHelper: 'Учетные записи резервного копирования управляются в настройках панели.', withImageHelper: 'Резервное копирование образов из магазина приложений увеличит размер файла снимка.', ignoreApp: 'Исключить приложения', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 17b1c36b6..a81e248ed 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -1135,6 +1135,7 @@ const message = { default_download_path: 'Varsayılan indirme bağlantısı', saveLocal: 'Yerel yedeklemeleri sakla (bulut depolama kopyalarının sayısı ile aynı)', url: 'URL Adresi', + urlHelper: 'Lütfen geçerli bir URL adresi girin', targetHelper: 'Yedekleme hesapları panel ayarlarında sürdürülür.', withImageHelper: 'Uygulama mağazası imajlarını yedekle, ancak bu anlık görüntü dosya boyutunu artıracaktır.', ignoreApp: 'Uygulamaları hariç tut', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 852bff84b..930342e08 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -1058,6 +1058,7 @@ const message = { default_download_path: '預設下載網址', saveLocal: '同時保留本機備份(和雲端儲存保留份數一致)', url: 'URL 地址', + urlHelper: '請輸入正確的 URL 地址', targetHelper: '備份帳號可在面板設定中維護', ignoreApp: '排除應用', withImage: '備份所有應用鏡像', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 4139c2de8..120e80bf3 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1060,6 +1060,7 @@ const message = { default_download_path: '默认下载地址', saveLocal: '同时保留本地备份(和云存储保留份数一致)', url: 'URL 地址', + urlHelper: '请输入正确的 URL 地址', targetHelper: '备份账号可在面板设置中维护', withImageHelper: '备份应用商店镜像,但是会增大快照文件体积。', ignoreApp: '排除应用', diff --git a/frontend/src/views/cronjob/cronjob/operate/index.vue b/frontend/src/views/cronjob/cronjob/operate/index.vue index a41620675..786187885 100644 --- a/frontend/src/views/cronjob/cronjob/operate/index.vue +++ b/frontend/src/views/cronjob/cronjob/operate/index.vue @@ -344,8 +344,21 @@ - - + +
+ + + +
+ + {{ $t('commons.button.add') }} +
@@ -837,6 +850,7 @@ const form = reactive({ dbType: 'mysql', dbName: '', url: '', + urlItems: [], isDir: true, files: [], sourceDir: '', @@ -896,6 +910,7 @@ const search = async () => { form.script = res.data.script; form.scriptMode = res.data.scriptMode; + form.urlItems = res.data.url.split(',') || []; form.containerName = res.data.containerName; form.user = res.data.user; @@ -1012,6 +1027,19 @@ const verifyScript = (rule: any, value: any, callback: any) => { } callback(); }; +const verifyUrlItems = (rule: any, value: any, callback: any) => { + if (!form.urlItems || form.urlItems.length === 0) { + callback(new Error(i18n.global.t('commons.rule.requiredInput'))); + return; + } + for (const item of form.urlItems) { + if (!item) { + callback(new Error(i18n.global.t('cronjob.urlHelper'))); + return; + } + } + callback(); +}; const verifySpec = (rule: any, value: any, callback: any) => { if (form.specCustom) { @@ -1151,7 +1179,7 @@ const rules = reactive({ websiteList: [Rules.requiredSelect], appIdList: [Rules.requiredSelect], dbNameList: [Rules.requiredSelect], - url: [Rules.requiredInput], + urlItems: [{ validator: verifyUrlItems, trigger: 'blur', required: true }], files: [{ validator: verifyFiles, trigger: 'blur', required: true }], sourceDir: [Rules.requiredInput], sourceAccountItems: [Rules.requiredSelect], @@ -1449,6 +1477,7 @@ const onSubmit = async (formEl: FormInstance | undefined) => { } form.sourceDir = files.join(','); } + form.url = form.urlItems.join(','); form.sourceAccountIDs = form.sourceAccountItems.join(','); form.spec = specs.join('&&'); if (!formEl) return;