feat: Support multiple URL access for cronjob (#11050)

Refs #10279
This commit is contained in:
ssongliu 2025-11-24 15:57:18 +08:00 committed by GitHub
parent d7c9b3b192
commit 3f47a6e701
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 64 additions and 7 deletions

View file

@ -195,10 +195,17 @@ func (u *CronjobService) handleShell(cronjob model.Cronjob, taskItem *task.Task)
} }
func (u *CronjobService) handleCurl(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 { urls := strings.Split(cronjob.URL, ",")
cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem)) for _, url := range urls {
return cmdMgr.Run("curl", cronjob.URL) if len(strings.TrimSpace(url)) == 0 {
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second) 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) { func (u *CronjobService) handleNtpSync(cronjob model.Cronjob, taskItem *task.Task) {

View file

@ -241,6 +241,7 @@ ErrUserFindErr: 'User {{ .name }} search failed {{ .err }}'
#cronjob #cronjob
CutWebsiteLogSuccess: '{{ .name }} website log cut successfully, backup path {{ .path }}' CutWebsiteLogSuccess: '{{ .name }} website log cut successfully, backup path {{ .path }}'
HandleShell: 'Execute script {{ .name }}' HandleShell: 'Execute script {{ .name }}'
HandleCurl: "Access URL {{ .name }}"
HandleNtpSync: 'System time synchronization' HandleNtpSync: 'System time synchronization'
HandleSystemClean: 'System cache cleanup' HandleSystemClean: 'System cache cleanup'
SystemLog: 'System Log' SystemLog: 'System Log'

View file

@ -240,6 +240,7 @@ ErrUserFindErr: 'Búsqueda del usuario {{ .name }} fallida {{ .err }}'
#cronjob #cronjob
CutWebsiteLogSuccess: 'Log del sitio web {{ .name }} cortado con éxito, ruta de respaldo {{ .path }}' CutWebsiteLogSuccess: 'Log del sitio web {{ .name }} cortado con éxito, ruta de respaldo {{ .path }}'
HandleShell: 'Ejecutar script {{ .name }}' HandleShell: 'Ejecutar script {{ .name }}'
HandleCurl: "URL {{ .name }} にアクセス"
HandleNtpSync: 'Sincronizar hora del sistema' HandleNtpSync: 'Sincronizar hora del sistema'
HandleSystemClean: 'Limpiar caché del sistema' HandleSystemClean: 'Limpiar caché del sistema'
SystemLog: 'Log del sistema' SystemLog: 'Log del sistema'

View file

@ -240,6 +240,7 @@ ErrUserFindErr: 'ユーザー {{ .name }} の検索に失敗しました {{ .err
#cronjob #cronjob
CutWebsiteLogSuccess: '{{ .name }} ウェブサイトのログが正常にカットされました。バックアップ パス {{ .path }}' CutWebsiteLogSuccess: '{{ .name }} ウェブサイトのログが正常にカットされました。バックアップ パス {{ .path }}'
HandleShell: 'スクリプト {{ .name }} を実行します' HandleShell: 'スクリプト {{ .name }} を実行します'
HandleCurl: "URL {{ .name }} にアクセス"
HandleNtpSync: 'システム時刻の同期' HandleNtpSync: 'システム時刻の同期'
HandleSystemClean: 'システム キャッシュのクリーンアップ' HandleSystemClean: 'システム キャッシュのクリーンアップ'
SystemLog: 'システムログ' SystemLog: 'システムログ'

View file

@ -241,6 +241,7 @@ ErrUserFindErr: '사용자 {{ .name }} 검색에 실패했습니다 {{ .err }}'
#크론잡 #크론잡
CutWebsiteLogSuccess: '{{ .name }} 웹사이트 로그가 성공적으로 잘렸습니다. 백업 경로 {{ .path }}' CutWebsiteLogSuccess: '{{ .name }} 웹사이트 로그가 성공적으로 잘렸습니다. 백업 경로 {{ .path }}'
HandleShell: '스크립트 {{ .name }} 실행' HandleShell: '스크립트 {{ .name }} 실행'
HandleCurl: "URL {{ .name }} 접근"
HandleNtpSync: '시스템 시간 동기화' HandleNtpSync: '시스템 시간 동기화'
HandleSystemClean: '시스템 캐시 정리' HandleSystemClean: '시스템 캐시 정리'
SystemLog: '시스템 로그' SystemLog: '시스템 로그'

View file

@ -241,6 +241,7 @@ ErrUserFindErr: 'Pengguna {{ .name }} carian gagal {{ .err }}'
#cronjob #cronjob
CutWebsiteLogSuccess: '{{ .name }} log tapak web berjaya dipotong, laluan sandaran {{ .path }}' CutWebsiteLogSuccess: '{{ .name }} log tapak web berjaya dipotong, laluan sandaran {{ .path }}'
HandleShell: 'Laksanakan skrip {{ .name }}' HandleShell: 'Laksanakan skrip {{ .name }}'
HandleCurl: "Akses URL {{ .name }}"
HandleNtpSync: 'Penyegerakan masa sistem' HandleNtpSync: 'Penyegerakan masa sistem'
HandleSystemClean: 'Pembersihan cache sistem' HandleSystemClean: 'Pembersihan cache sistem'
SystemLog: 'Log Sistem' SystemLog: 'Log Sistem'

View file

@ -241,6 +241,7 @@ ErrUserFindErr: 'Falha na pesquisa do usuário {{ .name }} {{ .err }}'
#cronjob #cronjob
CutWebsiteLogSuccess: '{{ .name }} registro do site cortado com sucesso, caminho de backup {{ .path }}' CutWebsiteLogSuccess: '{{ .name }} registro do site cortado com sucesso, caminho de backup {{ .path }}'
HandleShell: 'Executar script {{ .name }}' HandleShell: 'Executar script {{ .name }}'
HandleCurl: "Acessar URL {{ .name }}"
HandleNtpSync: 'Sincronização de hora do sistema' HandleNtpSync: 'Sincronização de hora do sistema'
HandleSystemClean: 'Limpeza de cache do sistema' HandleSystemClean: 'Limpeza de cache do sistema'
SystemLog: 'Log do Sistema' SystemLog: 'Log do Sistema'

View file

@ -241,6 +241,7 @@ ErrUserFindErr: 'Поиск пользователя {{ .name }} не удалс
#cronjob #cronjob
CutWebsiteLogSuccess: 'Журнал веб-сайта {{ .name }} успешно вырезан, путь к резервной копии {{ .path }}' CutWebsiteLogSuccess: 'Журнал веб-сайта {{ .name }} успешно вырезан, путь к резервной копии {{ .path }}'
HandleShell: 'Выполнить скрипт {{ .name }}' HandleShell: 'Выполнить скрипт {{ .name }}'
HandleCurl: "Доступ к URL {{ .name }}"
HandleNtpSync: 'Синхронизация системного времени' HandleNtpSync: 'Синхронизация системного времени'
HandleSystemClean: 'Очистка системного кэша' HandleSystemClean: 'Очистка системного кэша'
SystemLog: 'Системный лог' SystemLog: 'Системный лог'

View file

@ -242,6 +242,7 @@ ErrUserFindErr: 'Kullanıcı {{ .name }} arama başarısız {{ .err }}'
#cronjob #cronjob
CutWebsiteLogSuccess: '{{ .name }} web sitesi günlüğü başarıyla kesildi, yedekleme yolu {{ .path }}' CutWebsiteLogSuccess: '{{ .name }} web sitesi günlüğü başarıyla kesildi, yedekleme yolu {{ .path }}'
HandleShell: 'Betik {{ .name }} yürüt' HandleShell: 'Betik {{ .name }} yürüt'
HandleCurl: "URL'ye Eriş {{ .name }}"
HandleNtpSync: 'Sistem zaman senkronizasyonu' HandleNtpSync: 'Sistem zaman senkronizasyonu'
HandleSystemClean: 'Sistem önbellek temizliği' HandleSystemClean: 'Sistem önbellek temizliği'
SystemLog: 'Sistem Günlüğü' SystemLog: 'Sistem Günlüğü'

View file

@ -240,6 +240,7 @@ ErrUserFindErr: '使用者{{ .name }} 尋找失敗{{ .err }}'
#cronjob #cronjob
CutWebsiteLogSuccess: '{{ .name }} 網站日誌切割成功,備份路徑{{ .path }}' CutWebsiteLogSuccess: '{{ .name }} 網站日誌切割成功,備份路徑{{ .path }}'
HandleShell: '執行腳本{{ .name }}' HandleShell: '執行腳本{{ .name }}'
HandleCurl: "存取 URL {{ .name }}"
HandleNtpSync: '系統時間同步' HandleNtpSync: '系統時間同步'
HandleSystemClean: '系統快取清理' HandleSystemClean: '系統快取清理'
SystemLog: '系統日誌' SystemLog: '系統日誌'

View file

@ -241,6 +241,7 @@ ErrUserFindErr: "用户 {{ .name }} 查找失败 {{ .err }}"
#cronjob #cronjob
CutWebsiteLogSuccess: "{{ .name }} 网站日志切割成功,备份路径 {{ .path }}" CutWebsiteLogSuccess: "{{ .name }} 网站日志切割成功,备份路径 {{ .path }}"
HandleShell: "执行脚本 {{ .name }}" HandleShell: "执行脚本 {{ .name }}"
HandleCurl: "访问 URL {{ .name }}"
HandleNtpSync: "系统时间同步" HandleNtpSync: "系统时间同步"
HandleSystemClean: "系统缓存清理" HandleSystemClean: "系统缓存清理"
SystemLog: "系统日志" SystemLog: "系统日志"

View file

@ -34,6 +34,7 @@ export namespace Cronjob {
dbType: string; dbType: string;
dbName: string; dbName: string;
url: string; url: string;
urlItems: Array<string>;
isDir: boolean; isDir: boolean;
files: Array<Item>; files: Array<Item>;
sourceDir: string; sourceDir: string;

View file

@ -1120,6 +1120,7 @@ const message = {
default_download_path: 'Default download link', default_download_path: 'Default download link',
saveLocal: 'Retain local backups (the same as the number of cloud storage copies)', saveLocal: 'Retain local backups (the same as the number of cloud storage copies)',
url: 'URL Address', url: 'URL Address',
urlHelper: 'Please enter a valid URL address',
targetHelper: 'Backup accounts are maintained in panel settings.', targetHelper: 'Backup accounts are maintained in panel settings.',
withImageHelper: 'Backup app store images, but this will increase the snapshot file size.', withImageHelper: 'Backup app store images, but this will increase the snapshot file size.',
ignoreApp: 'Exclude apps', ignoreApp: 'Exclude apps',

View file

@ -1122,6 +1122,7 @@ const message = {
default_download_path: 'Enlace de descarga predeterminado', default_download_path: 'Enlace de descarga predeterminado',
saveLocal: 'Retener respaldos locales (igual al número de copias en la nube)', saveLocal: 'Retener respaldos locales (igual al número de copias en la nube)',
url: 'Dirección URL', 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.', targetHelper: 'Las cuentas de respaldo se gestionan en los ajustes del panel.',
withImageHelper: withImageHelper:
'Respalda imágenes de la tienda de aplicaciones, pero esto aumentará el tamaño del archivo de la instantánea.', 'Respalda imágenes de la tienda de aplicaciones, pero esto aumentará el tamaño del archivo de la instantánea.',

View file

@ -1090,6 +1090,7 @@ const message = {
default_download_path: 'デフォルトのダウンロードリンク', default_download_path: 'デフォルトのダウンロードリンク',
saveLocal: 'ローカルバックアップを保持しますクラウドストレージコピーの数と同じ', saveLocal: 'ローカルバックアップを保持しますクラウドストレージコピーの数と同じ',
url: 'URLアドレス', url: 'URLアドレス',
urlHelper: '正しいURLアドレスを入力してください',
targetHelper: 'バックアップアカウントはパネル設定で維持されます', targetHelper: 'バックアップアカウントはパネル設定で維持されます',
withImageHelper: withImageHelper:
'アプリストアのイメージをバックアップしますがスナップショットファイルのサイズが大きくなります', 'アプリストアのイメージをバックアップしますがスナップショットファイルのサイズが大きくなります',

View file

@ -1077,6 +1077,7 @@ const message = {
default_download_path: '기본 다운로드 링크', default_download_path: '기본 다운로드 링크',
saveLocal: '로컬 백업 보관 (클라우드 저장소 복사본 수와 동일)', saveLocal: '로컬 백업 보관 (클라우드 저장소 복사본 수와 동일)',
url: 'URL 주소', url: 'URL 주소',
urlHelper: '올바른 URL 주소를 입력해 주세요',
targetHelper: '백업 계정은 패널 설정에서 관리됩니다.', targetHelper: '백업 계정은 패널 설정에서 관리됩니다.',
withImageHelper: ' 스토어 이미지를 백업하지만 스냅샷 파일 크기가 증가합니다.', withImageHelper: ' 스토어 이미지를 백업하지만 스냅샷 파일 크기가 증가합니다.',
ignoreApp: ' 제외', ignoreApp: ' 제외',

View file

@ -1112,6 +1112,7 @@ const message = {
default_download_path: 'Pautan muat turun lalai', default_download_path: 'Pautan muat turun lalai',
saveLocal: 'Simpan sandaran tempatan (sama seperti bilangan salinan storan awan)', saveLocal: 'Simpan sandaran tempatan (sama seperti bilangan salinan storan awan)',
url: 'Alamat URL', url: 'Alamat URL',
urlHelper: 'Sila masukkan alamat URL yang sah',
targetHelper: 'Akaun sandaran diselenggara dalam tetapan panel.', targetHelper: 'Akaun sandaran diselenggara dalam tetapan panel.',
withImageHelper: 'Sandarkan imej kedai aplikasi, tetapi ini akan meningkatkan saiz fail snapshot.', withImageHelper: 'Sandarkan imej kedai aplikasi, tetapi ini akan meningkatkan saiz fail snapshot.',
ignoreApp: 'Kecualikan aplikasi', ignoreApp: 'Kecualikan aplikasi',

View file

@ -1110,6 +1110,7 @@ const message = {
default_download_path: 'Link de download padrão', default_download_path: 'Link de download padrão',
saveLocal: 'Manter backups locais (o mesmo número de cópias na nuvem)', saveLocal: 'Manter backups locais (o mesmo número de cópias na nuvem)',
url: 'Endereço URL', 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.', targetHelper: 'As contas de backup são mantidas nas configurações do painel.',
withImageHelper: withImageHelper:
'Fazer backup das imagens da loja de aplicativos, mas isso aumentará o tamanho do arquivo de snapshot.', 'Fazer backup das imagens da loja de aplicativos, mas isso aumentará o tamanho do arquivo de snapshot.',

View file

@ -1106,6 +1106,7 @@ const message = {
default_download_path: 'Ссылка для скачивания по умолчанию', default_download_path: 'Ссылка для скачивания по умолчанию',
saveLocal: 'Сохранять локальные резервные копии (столько же, сколько копий в облачном хранилище)', saveLocal: 'Сохранять локальные резервные копии (столько же, сколько копий в облачном хранилище)',
url: 'URL-адрес', url: 'URL-адрес',
urlHelper: 'Пожалуйста, введите действительный URL-адрес',
targetHelper: 'Учетные записи резервного копирования управляются в настройках панели.', targetHelper: 'Учетные записи резервного копирования управляются в настройках панели.',
withImageHelper: 'Резервное копирование образов из магазина приложений увеличит размер файла снимка.', withImageHelper: 'Резервное копирование образов из магазина приложений увеличит размер файла снимка.',
ignoreApp: 'Исключить приложения', ignoreApp: 'Исключить приложения',

View file

@ -1135,6 +1135,7 @@ const message = {
default_download_path: 'Varsayılan indirme bağlantısı', default_download_path: 'Varsayılan indirme bağlantısı',
saveLocal: 'Yerel yedeklemeleri sakla (bulut depolama kopyalarının sayısı ile aynı)', saveLocal: 'Yerel yedeklemeleri sakla (bulut depolama kopyalarının sayısı ile aynı)',
url: 'URL Adresi', url: 'URL Adresi',
urlHelper: 'Lütfen geçerli bir URL adresi girin',
targetHelper: 'Yedekleme hesapları panel ayarlarında sürdürülür.', 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.', withImageHelper: 'Uygulama mağazası imajlarını yedekle, ancak bu anlık görüntü dosya boyutunu artıracaktır.',
ignoreApp: 'Uygulamaları hariç tut', ignoreApp: 'Uygulamaları hariç tut',

View file

@ -1058,6 +1058,7 @@ const message = {
default_download_path: '預設下載網址', default_download_path: '預設下載網址',
saveLocal: '同時保留本機備份和雲端儲存保留份數一致', saveLocal: '同時保留本機備份和雲端儲存保留份數一致',
url: 'URL 地址', url: 'URL 地址',
urlHelper: '請輸入正確的 URL 地址',
targetHelper: '備份帳號可在面板設定中維護', targetHelper: '備份帳號可在面板設定中維護',
ignoreApp: '排除應用', ignoreApp: '排除應用',
withImage: '備份所有應用鏡像', withImage: '備份所有應用鏡像',

View file

@ -1060,6 +1060,7 @@ const message = {
default_download_path: '默认下载地址', default_download_path: '默认下载地址',
saveLocal: '同时保留本地备份和云存储保留份数一致', saveLocal: '同时保留本地备份和云存储保留份数一致',
url: 'URL 地址', url: 'URL 地址',
urlHelper: '请输入正确的 URL 地址',
targetHelper: '备份账号可在面板设置中维护', targetHelper: '备份账号可在面板设置中维护',
withImageHelper: '备份应用商店镜像但是会增大快照文件体积', withImageHelper: '备份应用商店镜像但是会增大快照文件体积',
ignoreApp: '排除应用', ignoreApp: '排除应用',

View file

@ -344,8 +344,21 @@
</el-form-item> </el-form-item>
</LayoutCol> </LayoutCol>
<LayoutCol v-if="form.type === 'curl'"> <LayoutCol v-if="form.type === 'curl'">
<el-form-item :label="$t('cronjob.url')" prop="url"> <el-form-item :label="$t('cronjob.url')" prop="urlItems">
<el-input clearable v-model.trim="form.url" /> <div v-for="(_, index) of form.urlItems" :key="index" class="w-full">
<el-input class="mt-2" v-model="form.urlItems[index]">
<template #append>
<el-button
link
icon="Delete"
@click="form.urlItems.splice(index, 1)"
/>
</template>
</el-input>
</div>
<el-button class="mt-2" @click="form.urlItems.push('')">
{{ $t('commons.button.add') }}
</el-button>
</el-form-item> </el-form-item>
</LayoutCol> </LayoutCol>
</el-row> </el-row>
@ -837,6 +850,7 @@ const form = reactive<Cronjob.CronjobInfo>({
dbType: 'mysql', dbType: 'mysql',
dbName: '', dbName: '',
url: '', url: '',
urlItems: [],
isDir: true, isDir: true,
files: [], files: [],
sourceDir: '', sourceDir: '',
@ -896,6 +910,7 @@ const search = async () => {
form.script = res.data.script; form.script = res.data.script;
form.scriptMode = res.data.scriptMode; form.scriptMode = res.data.scriptMode;
form.urlItems = res.data.url.split(',') || [];
form.containerName = res.data.containerName; form.containerName = res.data.containerName;
form.user = res.data.user; form.user = res.data.user;
@ -1012,6 +1027,19 @@ const verifyScript = (rule: any, value: any, callback: any) => {
} }
callback(); 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) => { const verifySpec = (rule: any, value: any, callback: any) => {
if (form.specCustom) { if (form.specCustom) {
@ -1151,7 +1179,7 @@ const rules = reactive({
websiteList: [Rules.requiredSelect], websiteList: [Rules.requiredSelect],
appIdList: [Rules.requiredSelect], appIdList: [Rules.requiredSelect],
dbNameList: [Rules.requiredSelect], dbNameList: [Rules.requiredSelect],
url: [Rules.requiredInput], urlItems: [{ validator: verifyUrlItems, trigger: 'blur', required: true }],
files: [{ validator: verifyFiles, trigger: 'blur', required: true }], files: [{ validator: verifyFiles, trigger: 'blur', required: true }],
sourceDir: [Rules.requiredInput], sourceDir: [Rules.requiredInput],
sourceAccountItems: [Rules.requiredSelect], sourceAccountItems: [Rules.requiredSelect],
@ -1449,6 +1477,7 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
} }
form.sourceDir = files.join(','); form.sourceDir = files.join(',');
} }
form.url = form.urlItems.join(',');
form.sourceAccountIDs = form.sourceAccountItems.join(','); form.sourceAccountIDs = form.sourceAccountItems.join(',');
form.spec = specs.join('&&'); form.spec = specs.join('&&');
if (!formEl) return; if (!formEl) return;