Repository: 1Panel-dev/1Panel
Pull request #8403: feat: Modify the way of creating cronjob
Author: login: ssongliu
Labels: name: do-not-merge/release-note-label-needed
name: approved
name: dev-v2
Created: 2025-04-16T02:26:03Z | Merged: 2025-04-16T06:00:17Z



Diff:
diff --git a/agent/app/api/v2/cronjob.go b/agent/app/api/v2/cronjob.go
index 2844abcf38dc..e18db07c8f55 100644
--- a/agent/app/api/v2/cronjob.go
+++ b/agent/app/api/v2/cronjob.go
@@ -12,14 +12,14 @@ import (
 // @Tags Cronjob
 // @Summary Create cronjob
 // @Accept json
-// @Param request body dto.CronjobCreate true "request"
+// @Param request body dto.CronjobOperate true "request"
 // @Success 200
 // @Security ApiKeyAuth
 // @Security Timestamp
 // @Router /cronjobs [post]
 // @x-panel-log {"bodyKeys":["type","name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建计划任务 [type][name]","formatEN":"create cronjob [type][name]"}
 func (b *BaseApi) CreateCronjob(c *gin.Context) {
-	var req dto.CronjobCreate
+	var req dto.CronjobOperate
 	if err := helper.CheckBindAndValidate(&req, c); err != nil {
 		return
 	}
@@ -31,6 +31,28 @@ func (b *BaseApi) CreateCronjob(c *gin.Context) {
 	helper.SuccessWithOutData(c)
 }
 
+// @Tags Cronjob
+// @Summary Load cronjob info
+// @Accept json
+// @Param request body dto.OperateByID true "request"
+// @Success 200
+// @Security ApiKeyAuth
+// @Security Timestamp
+// @Router /cronjobs/load/info [post]
+func (b *BaseApi) LoadCronjobInfo(c *gin.Context) {
+	var req dto.OperateByID
+	if err := helper.CheckBindAndValidate(&req, c); err != nil {
+		return
+	}
+
+	data, err := cronjobService.LoadInfo(req)
+	if err != nil {
+		helper.InternalServer(c, err)
+		return
+	}
+	helper.SuccessWithData(c, data)
+}
+
 // @Tags Cronjob
 // @Summary Load cronjob spec time
 // @Accept json
@@ -174,14 +196,14 @@ func (b *BaseApi) DeleteCronjob(c *gin.Context) {
 // @Tags Cronjob
 // @Summary Update cronjob
 // @Accept json
-// @Param request body dto.CronjobUpdate true "request"
+// @Param request body dto.CronjobOperate true "request"
 // @Success 200
 // @Security ApiKeyAuth
 // @Security Timestamp
 // @Router /cronjobs/update [post]
 // @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"更新计划任务 [name]","formatEN":"update cronjob [name]"}
 func (b *BaseApi) UpdateCronjob(c *gin.Context) {
-	var req dto.CronjobUpdate
+	var req dto.CronjobOperate
 	if err := helper.CheckBindAndValidate(&req, c); err != nil {
 		return
 	}
diff --git a/agent/app/dto/cronjob.go b/agent/app/dto/cronjob.go
index a5e1ed05d2c7..45705b9353f7 100644
--- a/agent/app/dto/cronjob.go
+++ b/agent/app/dto/cronjob.go
@@ -15,7 +15,8 @@ type CronjobSpec struct {
 	Spec string `json:"spec" validate:"required"`
 }
 
-type CronjobCreate struct {
+type CronjobOperate struct {
+	ID         uint   `json:"id"`
 	Name       string `json:"name" validate:"required"`
 	Type       string `json:"type" validate:"required"`
 	SpecCustom bool   `json:"specCustom"`
@@ -48,40 +49,6 @@ type CronjobCreate struct {
 	AlertTitle string `json:"alertTitle"`
 }
 
-type CronjobUpdate struct {
-	ID         uint   `json:"id" validate:"required"`
-	Type       string `json:"type" validate:"required"`
-	Name       string `json:"name" validate:"required"`
-	SpecCustom bool   `json:"specCustom"`
-	Spec       string `json:"spec" validate:"required"`
-
-	Executor      string `json:"executor"`
-	ScriptMode    string `json:"scriptMode"`
-	Script        string `json:"script"`
-	Command       string `json:"command"`
-	ContainerName string `json:"containerName"`
-	User          string `json:"user"`
-
-	AppID          string `json:"appID"`
-	Website        string `json:"website"`
-	ExclusionRules string `json:"exclusionRules"`
-	DBType         string `json:"dbType"`
-	DBName         string `json:"dbName"`
-	URL            string `json:"url"`
-	IsDir          bool   `json:"isDir"`
-	SourceDir      string `json:"sourceDir"`
-
-	SourceAccountIDs  string `json:"sourceAccountIDs"`
-	DownloadAccountID uint   `json:"downloadAccountID"`
-	RetainCopies      int    `json:"retainCopies" validate:"number,min=1"`
-	RetryTimes        int    `json:"retryTimes" validate:"number,min=0"`
-	Timeout           uint   `json:"timeout" validate:"number,min=1"`
-	Secret            string `json:"secret"`
-
-	AlertCount uint   `json:"alertCount"`
-	AlertTitle string `json:"alertTitle"`
-}
-
 type CronjobUpdateStatus struct {
 	ID     uint   `json:"id" validate:"required"`
 	Status string `json:"status" validate:"required"`
diff --git a/agent/app/service/cronjob.go b/agent/app/service/cronjob.go
index 646bf8040ec6..208d51236abe 100644
--- a/agent/app/service/cronjob.go
+++ b/agent/app/service/cronjob.go
@@ -27,16 +27,17 @@ type CronjobService struct{}
 type ICronjobService interface {
 	SearchWithPage(search dto.PageCronjob) (int64, interface{}, error)
 	SearchRecords(search dto.SearchRecord) (int64, interface{}, error)
-	Create(cronjobDto dto.CronjobCreate) error
+	Create(cronjobDto dto.CronjobOperate) error
 	LoadNextHandle(spec string) ([]string, error)
 	HandleOnce(id uint) error
-	Update(id uint, req dto.CronjobUpdate) error
+	Update(id uint, req dto.CronjobOperate) error
 	UpdateStatus(id uint, status string) error
 	Delete(req dto.CronjobBatchDelete) error
 	Download(down dto.CronjobDownload) (string, error)
 	StartJob(cronjob *model.Cronjob, isUpdate bool) (string, error)
 	CleanRecord(req dto.CronjobClean) error
 
+	LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, error)
 	LoadRecordLog(req dto.OperateByID) string
 }
 
@@ -75,6 +76,25 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
 	return total, dtoCronjobs, err
 }
 
+func (u *CronjobService) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, error) {
+	cronjob, err := cronjobRepo.Get(repo.WithByID(req.ID))
+	var item dto.CronjobOperate
+	if err := copier.Copy(&item, &cronjob); err != nil {
+		return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
+	}
+	alertBase := dto.AlertBase{
+		AlertType: cronjob.Type,
+		EntryID:   cronjob.ID,
+	}
+	alertCount := xpack.GetAlert(alertBase)
+	if alertCount != 0 {
+		item.AlertCount = alertCount
+	} else {
+		item.AlertCount = 0
+	}
+	return &item, err
+}
+
 func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interface{}, error) {
 	total, records, err := cronjobRepo.PageRecords(
 		search.Page,
@@ -216,7 +236,7 @@ func (u *CronjobService) HandleOnce(id uint) error {
 	return nil
 }
 
-func (u *CronjobService) Create(req dto.CronjobCreate) error {
+func (u *CronjobService) Create(req dto.CronjobOperate) error {
 	cronjob, _ := cronjobRepo.Get(repo.WithByName(req.Name))
 	if cronjob.ID != 0 {
 		return buserr.New("ErrRecordExist")
@@ -306,7 +326,7 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
 	return nil
 }
 
-func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
+func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
 	var cronjob model.Cronjob
 	if err := copier.Copy(&cronjob, &req); err != nil {
 		return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
@@ -353,6 +373,8 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
 	upMap["source_account_ids"] = req.SourceAccountIDs
 	upMap["download_account_id"] = req.DownloadAccountID
 	upMap["retain_copies"] = req.RetainCopies
+	upMap["retry_times"] = req.RetryTimes
+	upMap["timeout"] = req.Timeout
 	upMap["secret"] = req.Secret
 	err = cronjobRepo.Update(id, upMap)
 	if err != nil {
diff --git a/agent/router/ro_cronjob.go b/agent/router/ro_cronjob.go
index 667e71ead830..169c863a7c95 100644
--- a/agent/router/ro_cronjob.go
+++ b/agent/router/ro_cronjob.go
@@ -13,6 +13,7 @@ func (s *CronjobRouter) InitRouter(Router *gin.RouterGroup) {
 	{
 		cmdRouter.POST("", baseApi.CreateCronjob)
 		cmdRouter.POST("/next", baseApi.LoadNextHandle)
+		cmdRouter.POST("/load/info", baseApi.LoadCronjobInfo)
 		cmdRouter.POST("/del", baseApi.DeleteCronjob)
 		cmdRouter.POST("/update", baseApi.UpdateCronjob)
 		cmdRouter.POST("/status", baseApi.UpdateCronjobStatus)
diff --git a/agent/utils/cmd/cmdx.go b/agent/utils/cmd/cmdx.go
index dfb303eff3c2..28a86bb3f1b1 100644
--- a/agent/utils/cmd/cmdx.go
+++ b/agent/utils/cmd/cmdx.go
@@ -111,10 +111,10 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
 		cmd.Dir = c.workDir
 	}
 
-	if err := cmd.Start(); err != nil {
-		return "", err
-	}
 	if c.timeout != 0 {
+		if err := cmd.Start(); err != nil {
+			return "", err
+		}
 		done := make(chan error, 1)
 		go func() {
 			done <- cmd.Wait()
diff --git a/core/utils/cmd/cmdx.go b/core/utils/cmd/cmdx.go
index e541b1604559..285efa8db915 100644
--- a/core/utils/cmd/cmdx.go
+++ b/core/utils/cmd/cmdx.go
@@ -111,10 +111,10 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
 		cmd.Dir = c.workDir
 	}
 
-	if err := cmd.Start(); err != nil {
-		return "", err
-	}
 	if c.timeout != 0 {
+		if err := cmd.Start(); err != nil {
+			return "", err
+		}
 		done := make(chan error, 1)
 		go func() {
 			done <- cmd.Wait()
diff --git a/frontend/src/api/interface/cronjob.ts b/frontend/src/api/interface/cronjob.ts
index e17125428521..8164d18af669 100644
--- a/frontend/src/api/interface/cronjob.ts
+++ b/frontend/src/api/interface/cronjob.ts
@@ -36,6 +36,10 @@ export namespace Cronjob {
         sourceAccountItems: Array<number>;
 
         retainCopies: number;
+        retryTimes: number;
+        timeout: number;
+        timeoutItem: number;
+        timeoutUint: string;
         status: string;
         secret: string;
         hasAlert: boolean;
@@ -45,25 +49,41 @@ export namespace Cronjob {
     export interface Item {
         val: string;
     }
-    export interface CronjobCreate {
+    export interface CronjobOperate {
+        id: number;
         name: string;
         type: string;
         specCustom: boolean;
         spec: string;
+        specs: Array<string>;
         specObjs: Array<SpecObj>;
 
-        script: string;
+        appID: string;
         website: string;
         exclusionRules: string;
         dbType: string;
         dbName: string;
         url: string;
+        isDir: boolean;
         sourceDir: string;
 
+        //shell
+        executor: string;
+        scriptMode: string;
+        script: string;
+        command: string;
+        containerName: string;
+        user: string;
+
         sourceAccountIDs: string;
         downloadAccountID: number;
         retainCopies: number;
+        retryTimes: number;
+        timeout: number;
         secret: string;
+
+        alertCount: number;
+        alertTitle: string;
     }
     export interface SpecObj {
         specType: string;
@@ -73,24 +93,6 @@ export namespace Cronjob {
         minute: number;
         second: number;
     }
-    export interface CronjobUpdate {
-        id: number;
-        specCustom: boolean;
-        spec: string;
-
-        script: string;
-        website: string;
-        exclusionRules: string;
-        dbType: string;
-        dbName: string;
-        url: string;
-        sourceDir: string;
-
-        sourceAccountIDs: string;
-        downloadAccountID: number;
-        retainCopies: number;
-        secret: string;
-    }
     export interface CronjobDelete {
         ids: Array<number>;
         cleanData: boolean;
diff --git a/frontend/src/api/modules/cronjob.ts b/frontend/src/api/modules/cronjob.ts
index 2e0cdb72d4f0..617cb9527a38 100644
--- a/frontend/src/api/modules/cronjob.ts
+++ b/frontend/src/api/modules/cronjob.ts
@@ -11,15 +11,19 @@ export const loadNextHandle = (spec: string) => {
     return http.post<Array<String>>(`/cronjobs/next`, { spec: spec });
 };
 
+export const loadCronjobInfo = (id: number) => {
+    return http.post<Cronjob.CronjobOperate>(`/cronjobs/load/info`, { id: id });
+};
+
 export const getRecordLog = (id: number) => {
     return http.post<string>(`/cronjobs/records/log`, { id: id });
 };
 
-export const addCronjob = (params: Cronjob.CronjobCreate) => {
-    return http.post<Cronjob.CronjobCreate>(`/cronjobs`, params);
+export const addCronjob = (params: Cronjob.CronjobOperate) => {
+    return http.post<Cronjob.CronjobOperate>(`/cronjobs`, params);
 };
 
-export const editCronjob = (params: Cronjob.CronjobUpdate) => {
+export const editCronjob = (params: Cronjob.CronjobOperate) => {
     return http.post(`/cronjobs/update`, params);
 };
 
diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts
index 04e564bd3745..e29d9983d4ce 100644
--- a/frontend/src/lang/modules/en.ts
+++ b/frontend/src/lang/modules/en.ts
@@ -966,8 +966,7 @@ const message = {
         ntp_helper: 'You can configure the NTP server on the Quick Setup page of the Toolbox.',
         app: 'Backup App',
         website: 'Backup Website',
-        rulesHelper:
-            'When there are multiple compression exclusion rules, they need to be displayed with line breaks. For example: \n*.log \n*.sql',
+        rulesHelper: 'When multiple compression exclusion rules exist, separate them with [,]. Example: *.log,*.sql',
         lastRecordTime: 'Last Execution',
         all: 'All',
         failedRecord: 'Failed Records',
@@ -995,6 +994,9 @@ const message = {
         url: 'URL Address',
         targetHelper: 'Backup accounts are maintained in panel settings.',
         retainCopies: 'Retain Copies',
+        retryTimes: 'Retry Attempts',
+        timeout: 'Timeout',
+        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',
         retainCopiesUnit: ' copies (View)',
diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts
index 682b681fabf3..f5e5e64af8e9 100644
--- a/frontend/src/lang/modules/ja.ts
+++ b/frontend/src/lang/modules/ja.ts
@@ -935,7 +935,7 @@ const message = {
         ntp_helper: 'ツールボックスのクイックセットアップページでNTPサーバーを構成できます。',
         app: 'バックアップアプリ',
         website: 'バックアップウェブサイト',
-        rulesHelper: '圧縮除外ルールがある場合、ラインブレークで表示する必要があります。たとえば、 n*.log  n*.sql',
+        rulesHelper: '複数の圧縮除外ルールがある場合、[,]で区切ります。例: *.log,*.sql',
         lastRecordTime: '最後の実行時間',
         all: '全て',
         failedRecord: '失敗記録',
@@ -962,6 +962,9 @@ const message = {
         url: 'URLアドレス',
         targetHelper: 'バックアップアカウントは、パネル設定で維持されます。',
         retainCopies: '記録を保持します',
+        retryTimes: 'リトライ回数',
+        timeout: 'タイムアウト',
+        retryTimesHelper: '0は失敗後リトライしないことを意味します',
         retainCopiesHelper: '実行記録とログのために保持するコピーの数',
         retainCopiesHelper1: 'バックアップファイル用に保持するコピーの数',
         retainCopiesUnit: 'コピー（表示）',
diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts
index 8051fa025c99..dae6a7f38fe0 100644
--- a/frontend/src/lang/modules/ko.ts
+++ b/frontend/src/lang/modules/ko.ts
@@ -925,7 +925,7 @@ const message = {
         ntp_helper: 'Toolbox 의 빠른 설정 페이지에서 NTP 서버를 구성할 수 있습니다.',
         app: '백업 앱',
         website: '백업 웹사이트',
-        rulesHelper: '여러 개의 압축 제외 규칙이 있을 경우 줄 바꿈으로 표시해야 합니다. 예시:\n*.log \n*.sql',
+        rulesHelper: '여러 개의 압축 제외 규칙이 있는 경우 [,]로 구분하세요. 예: *.log,*.sql',
         lastRecordTime: '마지막 실행 시간',
         all: '전체',
         failedRecord: '실패한 레코드',
@@ -950,6 +950,9 @@ const message = {
         url: 'URL 주소',
         targetHelper: '백업 계정은 패널 설정에서 관리됩니다.',
         retainCopies: '기록 보관',
+        retryTimes: '재시도 횟수',
+        timeout: '타임아웃',
+        retryTimesHelper: '0은 실패 후 재시도 안 함을 의미합니다',
         retainCopiesHelper: '실행 기록과 로그에 대해 보관할 복사본 수',
         retainCopiesHelper1: '백업 파일에 대해 보관할 복사본 수',
         retainCopiesUnit: '개 (보기)',
diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts
index c4e459436d48..1e6c579fc8db 100644
--- a/frontend/src/lang/modules/ms.ts
+++ b/frontend/src/lang/modules/ms.ts
@@ -958,7 +958,7 @@ const message = {
         app: 'Aplikasi sandaran',
         website: 'Laman web sandaran',
         rulesHelper:
-            'Apabila terdapat pelbagai peraturan pengecualian mampatan, ia perlu dipaparkan dengan pemecahan baris. Sebagai contoh,\n*.log \n*.sql',
+            'Apabila terdapat pelbagai peraturan pengecualian mampatan, gunakan [,] sebagai pemisah. Contoh: *.log,*.sql',
         lastRecordTime: 'Waktu pelaksanaan terakhir',
         all: 'Semua',
         failedRecord: 'Rekod kegagalan',
@@ -985,6 +985,9 @@ const message = {
         url: 'Alamat URL',
         targetHelper: 'Akaun sandaran diselenggara dalam tetapan panel.',
         retainCopies: 'Simpan salinan',
+        retryTimes: 'Bilangan Cubaan Semula',
+        timeout: 'Masa Tamat',
+        retryTimesHelper: '0 bermaksud tiada cubaan semula selepas gagal',
         retainCopiesHelper: 'Bilangan salinan untuk menyimpan rekod pelaksanaan dan log',
         retainCopiesHelper1: 'Bilangan salinan untuk menyimpan fail sandaran',
         retainCopiesUnit: ' salinan (Lihat)',
diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts
index 15ee6cc35ba2..97c0f87e52b7 100644
--- a/frontend/src/lang/modules/pt-br.ts
+++ b/frontend/src/lang/modules/pt-br.ts
@@ -953,7 +953,7 @@ const message = {
         app: 'Backup de app',
         website: 'Backup de site',
         rulesHelper:
-            'Quando houver múltiplas regras de exclusão de compressão, elas devem ser exibidas com quebras de linha. Por exemplo,\n*.log\n*.sql',
+            'Quando existem múltiplas regras de exclusão de compactação, separe-as com [,]. Exemplo: *.log,*.sql',
         lastRecordTime: 'Última execução',
         all: 'Todos',
         failedRecord: 'Registros de falha',
@@ -980,6 +980,9 @@ const message = {
         url: 'Endereço URL',
         targetHelper: 'As contas de backup são mantidas nas configurações do painel.',
         retainCopies: 'Manter cópias',
+        retryTimes: 'Tentativas de Repetição',
+        timeout: 'Tempo Limite',
+        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',
         retainCopiesUnit: ' cópias (Visualizar)',
diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts
index c5df0510ec7f..4e25faf7a756 100644
--- a/frontend/src/lang/modules/ru.ts
+++ b/frontend/src/lang/modules/ru.ts
@@ -952,8 +952,7 @@ const message = {
         ntp_helper: 'Вы можете настроить NTP сервер на странице Быстрой настройки в Инструментах.',
         app: 'Резервное копирование приложения',
         website: 'Резервное копирование сайта',
-        rulesHelper:
-            'Когда существует несколько правил исключения сжатия, они должны отображаться с переносом строки. Например,\n*.log \n*.sql',
+        rulesHelper: 'При наличии нескольких правил исключения сжатия разделяйте их [,]. Пример: *.log,*.sql',
         lastRecordTime: 'Время последнего выполнения',
         all: 'Все',
         failedRecord: 'Неудачные записи',
@@ -980,6 +979,9 @@ const message = {
         url: 'URL-адрес',
         targetHelper: 'Учетные записи резервного копирования управляются в настройках панели.',
         retainCopies: 'Сохранять записи',
+        retryTimes: 'Количество повторов',
+        timeout: 'Таймаут',
+        retryTimesHelper: '0 означает отсутствие повторов после сбоя',
         retainCopiesHelper: 'Количество копий для сохранения записей выполнения и логов',
         retainCopiesHelper1: 'Количество копий для сохранения файлов резервных копий',
         retainCopiesUnit: ' копий (Просмотр)',
diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts
index 0da4e309bb64..23e3b6796a95 100644
--- a/frontend/src/lang/modules/zh-Hant.ts
+++ b/frontend/src/lang/modules/zh-Hant.ts
@@ -920,7 +920,7 @@ const message = {
         ntp_helper: '您可以在工具箱的快速設定頁面配置 NTP 伺服器',
         app: '備份應用',
         website: '備份網站',
-        rulesHelper: '當存在多個壓縮排除規則時，需要換行顯示，例：\n*.log \n*.sql',
+        rulesHelper: '當存在多個壓縮排除規則時，使用[,]分隔，例：*.log,*.sql',
         lastRecordTime: '上次執行情況',
         database: '備份數據庫',
         missBackupAccount: '未能找到備份賬號',
@@ -943,6 +943,9 @@ const message = {
         url: 'URL 地址',
         targetHelper: '備份賬號可在面板設置中維護',
         retainCopies: '保留份數',
+        retryTimes: '失敗重試次數',
+        timeout: '逾時時間',
+        retryTimesHelper: '為0表示失敗後不重試',
         retainCopiesHelper: '執行記錄及日誌保留份数',
         retainCopiesHelper1: '備份文件保留份数',
         retainCopiesUnit: ' 份 (查看)',
diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts
index 1730527920fb..f7cb5ac4f303 100644
--- a/frontend/src/lang/modules/zh.ts
+++ b/frontend/src/lang/modules/zh.ts
@@ -918,7 +918,7 @@ const message = {
         ntp_helper: '您可以在工具箱的快速设置页面配置 NTP 服务器',
         app: '备份应用',
         website: '备份网站',
-        rulesHelper: '当存在多个压缩排除规则时，需要换行显示，例：\n*.log \n*.sql',
+        rulesHelper: '当存在多个压缩排除规则时，使用 [,] 分隔，例：*.log,*.sql',
         lastRecordTime: '上次执行情况',
         database: '备份数据库',
         missBackupAccount: '未能找到备份账号',
@@ -941,6 +941,9 @@ const message = {
         url: 'URL 地址',
         targetHelper: '备份账号可在面板设置中维护',
         retainCopies: '保留份数',
+        retryTimes: '失败重试次数',
+        timeout: '超时时间',
+        retryTimesHelper: '为 0 表示失败后不重试',
         retainCopiesHelper: '执行记录及日志保留份数',
         retainCopiesHelper1: '备份文件保留份数',
         retainCopiesUnit: ' 份 (查看)',
diff --git a/frontend/src/routers/index.ts b/frontend/src/routers/index.ts
index 9e38c1c3962a..95ff1230e2ee 100644
--- a/frontend/src/routers/index.ts
+++ b/frontend/src/routers/index.ts
@@ -76,7 +76,13 @@ router.afterEach((to) => {
             notMathParam = false;
         }
         if (notMathParam) {
-            localStorage.setItem('cachedRoute' + to.meta.activeMenu, to.path);
+            if (to.meta.activeMenu === '/cronjobs' && to.path === '/cronjobs/cronjob/operate') {
+                localStorage.setItem('cachedRoute' + to.meta.activeMenu, '/cronjobs/cronjob');
+            } else if (to.meta.activeMenu === '/containers' && to.path === '/containers/container/operate') {
+                localStorage.setItem('cachedRoute' + to.meta.activeMenu, '/containers/container');
+            } else {
+                localStorage.setItem('cachedRoute' + to.meta.activeMenu, to.path);
+            }
         }
     }
 
diff --git a/frontend/src/routers/modules/cronjob.ts b/frontend/src/routers/modules/cronjob.ts
index 774d3f30703e..df00f68d666c 100644
--- a/frontend/src/routers/modules/cronjob.ts
+++ b/frontend/src/routers/modules/cronjob.ts
@@ -28,6 +28,16 @@ const cronRouter = {
                         requiresAuth: false,
                     },
                 },
+                {
+                    path: 'cronjob/operate',
+                    name: 'CronjobOperate',
+                    component: () => import('@/views/cronjob/cronjob/operate/index.vue'),
+                    hidden: true,
+                    meta: {
+                        activeMenu: '/cronjobs',
+                        requiresAuth: false,
+                    },
+                },
                 {
                     path: 'library',
                     name: 'Library',
diff --git a/frontend/src/utils/util.ts b/frontend/src/utils/util.ts
index 9da5636dea9c..532d0852d7c5 100644
--- a/frontend/src/utils/util.ts
+++ b/frontend/src/utils/util.ts
@@ -444,6 +444,21 @@ export function getProvider(provider: string): string {
     }
 }
 
+export function transferTimeToSecond(item: string): any {
+    if (item.indexOf('s') !== -1) {
+        return Number(item.replaceAll('s', ''));
+    }
+    if (item.indexOf('m') !== -1) {
+        return Number(item.replaceAll('m', '')) * 60;
+    }
+    if (item.indexOf('h') !== -1) {
+        return Number(item.replaceAll('h', '')) * 60 * 60;
+    }
+    if (item.indexOf('d') !== -1) {
+        return Number(item.replaceAll('d', '')) * 60 * 60 * 24;
+    }
+    return Number(item);
+}
 export function splitTime(item: string): any {
     if (item.indexOf('s') !== -1) {
         return { time: Number(item.replaceAll('s', '')), unit: 's' };
diff --git a/frontend/src/views/cronjob/cronjob/index.vue b/frontend/src/views/cronjob/cronjob/index.vue
index d9fb1604af9f..558caf52ef91 100644
--- a/frontend/src/views/cronjob/cronjob/index.vue
+++ b/frontend/src/views/cronjob/cronjob/index.vue
@@ -2,7 +2,7 @@
     <div>
         <LayoutContent v-loading="loading" v-if="!isRecordShow" :title="$t('menu.cronjob')">
             <template #leftToolBar>
-                <el-button type="primary" @click="onOpenDialog('create')">
+                <el-button type="primary" @click="onOpenDialog('')">
                     {{ $t('commons.button.create') }}{{ $t('menu.cronjob') }}
                 </el-button>
                 <el-button-group class="ml-4">
@@ -150,14 +150,12 @@
                 </el-form>
             </template>
         </OpDialog>
-        <OperateDialog @search="search" ref="dialogRef" />
         <Records @search="search" ref="dialogRecordRef" />
         <Backups @search="search" ref="dialogBackupRef" />
     </div>
 </template>
 
 <script lang="ts" setup>
-import OperateDialog from '@/views/cronjob/cronjob/operate/index.vue';
 import Records from '@/views/cronjob/cronjob/record/index.vue';
 import Backups from '@/views/cronjob/cronjob/backup/index.vue';
 import { computed, onMounted, reactive, ref } from 'vue';
@@ -168,6 +166,7 @@ import { ElMessageBox } from 'element-plus';
 import { MsgSuccess } from '@/utils/message';
 import { transSpecToStr } from './helper';
 import { GlobalStore } from '@/store';
+import router from '@/routers';
 
 const globalStore = GlobalStore();
 const mobile = computed(() => {
@@ -219,29 +218,8 @@ const search = async (column?: any) => {
 const dialogRecordRef = ref();
 const dialogBackupRef = ref();
 
-const dialogRef = ref();
-const onOpenDialog = async (
-    title: string,
-    rowData: Partial<Cronjob.CronjobInfo> = {
-        specObjs: [
-            {
-                specType: 'perMonth',
-                week: 1,
-                day: 3,
-                hour: 1,
-                minute: 30,
-                second: 30,
-            },
-        ],
-        type: 'shell',
-        retainCopies: 7,
-    },
-) => {
-    let params = {
-        title,
-        rowData: { ...rowData },
-    };
-    dialogRef.value!.acceptParams(params);
+const onOpenDialog = async (id: string) => {
+    router.push({ name: 'CronjobOperate', query: { id: id } });
 };
 
 const onDelete = async (row: Cronjob.CronjobInfo | null) => {
@@ -362,7 +340,7 @@ const buttons = [
     {
         label: i18n.global.t('commons.button.edit'),
         click: (row: Cronjob.CronjobInfo) => {
-            onOpenDialog('edit', row);
+            onOpenDialog(row.id + '');
         },
     },
     {
diff --git a/frontend/src/views/cronjob/cronjob/operate/index.vue b/frontend/src/views/cronjob/cronjob/operate/index.vue
index 5f944905af14..7b41a900822f 100644
--- a/frontend/src/views/cronjob/cronjob/operate/index.vue
+++ b/frontend/src/views/cronjob/cronjob/operate/index.vue
@@ -1,528 +1,633 @@
 <template>
-    <DrawerPro
-        v-model="drawerVisible"
-        :header="title"
-        :resource="dialogData.title === 'create' ? '' : dialogData.rowData?.name"
-        @close="handleClose"
-        size="large"
+    <LayoutContent
+        back-name="CronjobItem"
+        :title="isCreate ? $t('cronjob.create') : $t('commons.button.edit') + ' - ' + form.name"
     >
-        <el-form ref="formRef" label-position="top" :model="dialogData.rowData" :rules="rules">
-            <el-form-item :label="$t('cronjob.taskType')" prop="type">
-                <el-select
-                    v-if="dialogData.title === 'create'"
-                    class="selectClass"
-                    @change="changeType"
-                    v-model="dialogData.rowData!.type"
-                >
-                    <el-option value="shell" :label="$t('cronjob.shell')" />
-                    <el-option value="app" :label="$t('cronjob.app')" />
-                    <el-option value="website" :label="$t('cronjob.website')" />
-                    <el-option value="database" :label="$t('cronjob.database')" />
-                    <el-option value="directory" :label="$t('cronjob.directory')" />
-                    <el-option value="log" :label="$t('cronjob.log')" />
-                    <el-option value="curl" :label="$t('cronjob.curl')" />
-                    <el-option value="cutWebsiteLog" :label="$t('cronjob.cutWebsiteLog')" />
-                    <el-option value="clean" :label="$t('setting.diskClean')" />
-                    <el-option value="snapshot" :label="$t('cronjob.snapshot')" />
-                    <el-option value="ntp" :label="$t('cronjob.ntp')" />
-                </el-select>
-                <div v-else class="w-full">
-                    <el-tag>{{ $t('cronjob.' + dialogData.rowData!.type) }}</el-tag>
-                </div>
-                <div v-if="dialogData.rowData!.type === 'log'" class="logText">
-                    <span class="input-help">
-                        {{ $t('cronjob.logHelper1') }}
-                        <el-link class="link" icon="Position" @click="goRouter('/logs/system')" type="primary">
-                            {{ $t('firewall.quickJump') }}
-                        </el-link>
-                    </span>
-                    <span class="input-help">
-                        {{ $t('cronjob.logHelper2') }}
-                        <el-link class="link" icon="Position" @click="goRouter('/logs/ssh')" type="primary">
-                            {{ $t('firewall.quickJump') }}
-                        </el-link>
-                    </span>
-                    <span class="input-help">
-                        {{ $t('cronjob.logHelper3') }}
-                        <el-link class="link" icon="Position" @click="goRouter('/logs/website')" type="primary">
-                            {{ $t('firewall.quickJump') }}
-                        </el-link>
-                    </span>
-                </div>
-                <div v-if="dialogData.rowData!.type === 'ntp'">
-                    <span class="input-help">
-                        {{ $t('cronjob.ntp_helper') }}
-                        <el-link
-                            style="font-size: 12px"
-                            icon="Position"
-                            @click="goRouter('/toolbox/device')"
-                            type="primary"
-                        >
-                            {{ $t('firewall.quickJump') }}
-                        </el-link>
-                    </span>
-                </div>
-            </el-form-item>
-
-            <el-form-item :label="$t('cronjob.taskName')" prop="name">
-                <el-input :disabled="dialogData.title === 'edit'" clearable v-model.trim="dialogData.rowData!.name" />
-            </el-form-item>
-            <el-card class="mb-5">
-                <el-form-item :label="$t('cronjob.cronSpec')" prop="specCustom">
-                    <el-checkbox :label="$t('container.custom')" v-model="dialogData.rowData!.specCustom" />
-                </el-form-item>
-                <div v-if="!dialogData.rowData!.specCustom">
-                    <el-form-item prop="spec">
-                        <div v-for="(specObj, index) of dialogData.rowData.specObjs" :key="index" style="width: 100%">
-                            <el-select class="specTypeClass" v-model="specObj.specType" @change="changeSpecType(index)">
-                                <el-option
-                                    v-for="item in specOptions"
-                                    :key="item.label"
-                                    :value="item.value"
-                                    :label="item.label"
-                                />
-                            </el-select>
-                            <el-select v-if="specObj.specType === 'perWeek'" class="specClass" v-model="specObj.week">
-                                <el-option
-                                    v-for="item in weekOptions"
-                                    :key="item.label"
-                                    :value="item.value"
-                                    :label="item.label"
-                                />
-                            </el-select>
-                            <el-input v-if="hasDay(specObj)" class="specClass" v-model.number="specObj.day">
-                                <template #append>
-                                    <div class="append">{{ $t('cronjob.day') }}</div>
-                                </template>
-                            </el-input>
-                            <el-input v-if="hasHour(specObj)" class="specClass" v-model.number="specObj.hour">
-                                <template #append>
-                                    <div class="append">{{ $t('commons.units.hour') }}</div>
-                                </template>
-                            </el-input>
-                            <el-input
-                                v-if="specObj.specType !== 'perNSecond'"
-                                class="specClass"
-                                v-model.number="specObj.minute"
-                            >
-                                <template #append>
-                                    <div class="append">{{ $t('commons.units.minute') }}</div>
-                                </template>
-                            </el-input>
-                            <el-input
-                                v-if="specObj.specType === 'perNSecond'"
-                                class="specClass"
-                                v-model.number="specObj.second"
-                            >
-                                <template #append>
-                                    <div class="append">{{ $t('commons.units.second') }}</div>
-                                </template>
-                            </el-input>
-                            <el-popover
-                                placement="top-start"
-                                :title="$t('cronjob.nextTime')"
-                                width="200"
-                                trigger="click"
-                            >
-                                <div v-for="(time, index_t) of nextTimes" :key="index_t">
-                                    <el-tag class="mt-2">{{ time }}</el-tag>
-                                </div>
-                                <template #reference>
-                                    <el-button class="ml-2.5" @click="loadNext(specObj)" link type="primary">
-                                        {{ $t('commons.button.preview') }}
-                                    </el-button>
-                                </template>
-                            </el-popover>
-                            <el-button
-                                class="ml-2.5"
-                                link
-                                type="primary"
-                                @click="handleSpecDelete(index)"
-                                v-if="dialogData.rowData.specObjs.length > 1"
-                            >
-                                {{ $t('commons.button.delete') }}
-                            </el-button>
-                            <el-divider v-if="dialogData.rowData.specObjs.length > 1" class="divider" />
-                        </div>
-                    </el-form-item>
-                    <el-button class="mb-3" @click="handleSpecAdd()">
-                        {{ $t('commons.button.add') }}
-                    </el-button>
-                </div>
-
-                <div v-if="dialogData.rowData!.specCustom">
-                    <el-form-item prop="spec">
-                        <div v-for="(spec, index) of dialogData.rowData.specs" :key="index" class="w-full">
-                            <el-input class="specCustom" v-model="dialogData.rowData.specs[index]" />
-                            <el-popover
-                                placement="top-start"
-                                :title="$t('cronjob.nextTime')"
-                                width="200"
-                                trigger="click"
-                            >
-                                <div v-for="(time, index_t) of nextTimes" :key="index_t">
-                                    <el-tag class="mt-2">{{ time }}</el-tag>
-                                </div>
-                                <template #reference>
-                                    <el-button class="ml-2.5" @click="loadNext(spec)" link type="primary">
-                                        {{ $t('commons.button.preview') }}
-                                    </el-button>
-                                </template>
-                            </el-popover>
-                            <el-button
-                                class="ml-2.5"
-                                link
-                                type="primary"
-                                @click="handleSpecCustomDelete(index)"
-                                v-if="dialogData.rowData.specs.length > 1"
-                            >
-                                {{ $t('commons.button.delete') }}
-                            </el-button>
-                            <el-divider v-if="dialogData.rowData.specs.length > 1" class="divider" />
-                        </div>
-                    </el-form-item>
-                    <el-button class="mb-3" @click="handleSpecCustomAdd()">
-                        {{ $t('commons.button.add') }}
-                    </el-button>
-                </div>
-            </el-card>
-
-            <div v-if="hasScript()">
-                <el-form-item class="mt-5">
-                    <el-checkbox v-model="dialogData.rowData!.inContainer">
-                        {{ $t('cronjob.containerCheckBox') }}
-                    </el-checkbox>
-                </el-form-item>
-                <el-card v-if="dialogData.rowData!.inContainer">
-                    <el-row :gutter="20">
-                        <el-col :span="12">
-                            <el-form-item :label="$t('cronjob.containerName')" prop="containerName">
-                                <el-select class="selectClass" v-model="dialogData.rowData!.containerName">
-                                    <el-option
-                                        v-for="item in containerOptions"
-                                        :key="item"
-                                        :value="item"
-                                        :label="item"
-                                    />
-                                </el-select>
-                            </el-form-item>
-                        </el-col>
-                        <el-col :span="12">
-                            <el-form-item :label="$t('container.command')" prop="command" :rules="Rules.requiredInput">
-                                <el-checkbox border v-model="dialogData.rowData!.isCustom">
-                                    {{ $t('container.custom') }}
-                                </el-checkbox>
+        <template #main>
+            <el-form ref="formRef" label-position="top" :model="form" :rules="rules">
+                <el-row type="flex" justify="center" :gutter="20">
+                    <el-col :span="20">
+                        <el-card>
+                            <el-form-item :label="$t('cronjob.taskType')" prop="type">
                                 <el-select
-                                    v-if="!dialogData.rowData!.isCustom"
-                                    style="width: calc(100% - 100px)"
-                                    filterable
-                                    clearable
-                                    v-model="dialogData.rowData!.command"
+                                    v-if="isCreate"
+                                    class="mini-form-item"
+                                    @change="changeType"
+                                    v-model="form.type"
                                 >
-                                    <el-option value="ash" label="/bin/ash" />
-                                    <el-option value="bash" label="/bin/bash" />
-                                    <el-option value="sh" label="/bin/sh" />
+                                    <el-option value="shell" :label="$t('cronjob.shell')" />
+                                    <el-option value="app" :label="$t('cronjob.app')" />
+                                    <el-option value="website" :label="$t('cronjob.website')" />
+                                    <el-option value="database" :label="$t('cronjob.database')" />
+                                    <el-option value="directory" :label="$t('cronjob.directory')" />
+                                    <el-option value="log" :label="$t('cronjob.log')" />
+                                    <el-option value="curl" :label="$t('cronjob.curl')" />
+                                    <el-option value="cutWebsiteLog" :label="$t('cronjob.cutWebsiteLog')" />
+                                    <el-option value="clean" :label="$t('setting.diskClean')" />
+                                    <el-option value="snapshot" :label="$t('cronjob.snapshot')" />
+                                    <el-option value="ntp" :label="$t('cronjob.ntp')" />
                                 </el-select>
+                                <div v-else class="w-full">
+                                    <el-tag>{{ $t('cronjob.' + form.type) }}</el-tag>
+                                </div>
+                                <span class="input-help logText" v-if="form.type === 'log'">
+                                    {{ $t('cronjob.logHelper1') }}
+                                    <el-link
+                                        class="link"
+                                        icon="Position"
+                                        @click="goRouter('/logs/system')"
+                                        type="primary"
+                                    >
+                                        {{ $t('firewall.quickJump') }}
+                                    </el-link>
+                                </span>
+                                <span class="input-help logText" v-if="form.type === 'log'">
+                                    {{ $t('cronjob.logHelper2') }}
+                                    <el-link class="link" icon="Position" @click="goRouter('/logs/ssh')" type="primary">
+                                        {{ $t('firewall.quickJump') }}
+                                    </el-link>
+                                </span>
+                                <span class="input-help logText" v-if="form.type === 'log'">
+                                    {{ $t('cronjob.logHelper3') }}
+                                    <el-link
+                                        class="link"
+                                        icon="Position"
+                                        @click="goRouter('/logs/website')"
+                                        type="primary"
+                                    >
+                                        {{ $t('firewall.quickJump') }}
+                                    </el-link>
+                                </span>
+                                <span class="input-help logText" v-if="form.type === 'ntp'">
+                                    {{ $t('cronjob.ntp_helper') }}
+                                    <el-link
+                                        class="link"
+                                        icon="Position"
+                                        @click="goRouter('/toolbox/device')"
+                                        type="primary"
+                                    >
+                                        {{ $t('firewall.quickJump') }}
+                                    </el-link>
+                                </span>
+                            </el-form-item>
+                            <el-form-item :label="$t('cronjob.taskName')" prop="name">
                                 <el-input
+                                    class="mini-form-item"
+                                    :disabled="!isCreate"
                                     clearable
-                                    v-else
-                                    style="width: calc(100% - 100px)"
-                                    v-model="dialogData.rowData!.command"
+                                    v-model.trim="form.name"
                                 />
                             </el-form-item>
-                        </el-col>
-                    </el-row>
-                </el-card>
-                <div v-if="!dialogData.rowData!.inContainer">
-                    <el-card>
-                        <el-row :gutter="20">
-                            <el-col :span="12">
-                                <el-form-item :label="$t('commons.table.user')" prop="user">
-                                    <el-select filterable v-model="dialogData.rowData!.user">
-                                        <div v-for="item in userOptions" :key="item">
-                                            <el-option :value="item" :label="item" />
-                                        </div>
-                                    </el-select>
+                        </el-card>
+
+                        <el-card class="mt-5">
+                            <el-form-item :label="$t('cronjob.cronSpec')" prop="specCustom">
+                                <el-checkbox :label="$t('container.custom')" v-model="form.specCustom" />
+                            </el-form-item>
+                            <div v-if="!form.specCustom">
+                                <el-form-item prop="spec">
+                                    <div v-for="(specObj, index) of form.specObjs" :key="index" style="width: 100%">
+                                        <el-select
+                                            class="specTypeClass"
+                                            v-model="specObj.specType"
+                                            @change="changeSpecType(index)"
+                                        >
+                                            <el-option
+                                                v-for="item in specOptions"
+                                                :key="item.label"
+                                                :value="item.value"
+                                                :label="item.label"
+                                            />
+                                        </el-select>
+                                        <el-select
+                                            v-if="specObj.specType === 'perWeek'"
+                                            class="specClass"
+                                            v-model="specObj.week"
+                                        >
+                                            <el-option
+                                                v-for="item in weekOptions"
+                                                :key="item.label"
+                                                :value="item.value"
+                                                :label="item.label"
+                                            />
+                                        </el-select>
+                                        <el-input v-if="hasDay(specObj)" class="specClass" v-model.number="specObj.day">
+                                            <template #append>
+                                                <div class="append">{{ $t('cronjob.day') }}</div>
+                                            </template>
+                                        </el-input>
+                                        <el-input
+                                            v-if="hasHour(specObj)"
+                                            class="specClass"
+                                            v-model.number="specObj.hour"
+                                        >
+                                            <template #append>
+                                                <div class="append">{{ $t('commons.units.hour') }}</div>
+                                            </template>
+                                        </el-input>
+                                        <el-input
+                                            v-if="specObj.specType !== 'perNSecond'"
+                                            class="specClass"
+                                            v-model.number="specObj.minute"
+                                        >
+                                            <template #append>
+                                                <div class="append">{{ $t('commons.units.minute') }}</div>
+                                            </template>
+                                        </el-input>
+                                        <el-input
+                                            v-if="specObj.specType === 'perNSecond'"
+                                            class="specClass"
+                                            v-model.number="specObj.second"
+                                        >
+                                            <template #append>
+                                                <div class="append">{{ $t('commons.units.second') }}</div>
+                                            </template>
+                                        </el-input>
+                                        <el-popover
+                                            placement="top-start"
+                                            :title="$t('cronjob.nextTime')"
+                                            width="200"
+                                            trigger="click"
+                                        >
+                                            <div v-for="(time, index_t) of nextTimes" :key="index_t">
+                                                <el-tag class="mt-2">{{ time }}</el-tag>
+                                            </div>
+                                            <template #reference>
+                                                <el-button class="ml-5" @click="loadNext(specObj)" link type="primary">
+                                                    {{ $t('commons.button.preview') }}
+                                                </el-button>
+                                            </template>
+                                        </el-popover>
+                                        <el-button
+                                            class="ml-2.5"
+                                            link
+                                            type="primary"
+                                            @click="handleSpecDelete(index)"
+                                            v-if="form.specObjs.length > 1"
+                                        >
+                                            {{ $t('commons.button.delete') }}
+                                        </el-button>
+                                        <el-divider v-if="form.specObjs.length > 1" class="divider" />
+                                    </div>
+                                </el-form-item>
+                                <el-button class="mb-3" @click="handleSpecAdd()">
+                                    {{ $t('commons.button.add') }}
+                                </el-button>
+                            </div>
+
+                            <div v-if="form.specCustom">
+                                <el-form-item prop="spec">
+                                    <div v-for="(spec, index) of form.specs" :key="index" class="w-full">
+                                        <el-input class="specCustom" v-model="form.specs[index]" />
+                                        <el-popover
+                                            placement="top-start"
+                                            :title="$t('cronjob.nextTime')"
+                                            width="200"
+                                            trigger="click"
+                                        >
+                                            <div v-for="(time, index_t) of nextTimes" :key="index_t">
+                                                <el-tag class="mt-2">{{ time }}</el-tag>
+                                            </div>
+                                            <template #reference>
+                                                <el-button class="ml-2.5" @click="loadNext(spec)" link type="primary">
+                                                    {{ $t('commons.button.preview') }}
+                                                </el-button>
+                                            </template>
+                                        </el-popover>
+                                        <el-button
+                                            class="ml-2.5"
+                                            link
+                                            type="primary"
+                                            @click="handleSpecCustomDelete(index)"
+                                            v-if="form.specs.length > 1"
+                                        >
+                                            {{ $t('commons.button.delete') }}
+                                        </el-button>
+                                        <el-divider v-if="form.specs.length > 1" class="divider" />
+                                    </div>
                                 </el-form-item>
-                            </el-col>
-                            <el-col :span="12">
-                                <el-form-item :label="$t('cronjob.executor')" prop="executor">
-                                    <el-checkbox border v-model="dialogData.rowData!.isExecutorCustom">
-                                        {{ $t('container.custom') }}
-                                    </el-checkbox>
-                                    <el-select
-                                        v-if="!dialogData.rowData!.isExecutorCustom"
-                                        style="width: calc(100% - 100px)"
-                                        v-model="dialogData.rowData!.executor"
+                                <el-button class="mb-3" @click="handleSpecCustomAdd()">
+                                    {{ $t('commons.button.add') }}
+                                </el-button>
+                            </div>
+                        </el-card>
+
+                        <el-card class="mt-5">
+                            <el-row :gutter="20">
+                                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="isWebsite()">
+                                    <el-form-item
+                                        :label="form.type === 'website' ? $t('cronjob.website') : $t('menu.website')"
+                                        prop="website"
                                     >
-                                        <el-option value="bash" label="bash" />
-                                        <el-option value="python" label="python" />
-                                        <el-option value="sh" label="sh" />
-                                    </el-select>
-                                    <el-input
-                                        clearable
-                                        v-else
-                                        style="width: calc(100% - 100px)"
-                                        v-model="dialogData.rowData!.executor"
+                                        <el-select v-model="form.website">
+                                            <el-option
+                                                :disabled="websiteOptions.length === 0"
+                                                :label="$t('commons.table.all')"
+                                                value="all"
+                                            />
+                                            <el-option
+                                                v-for="(item, index) in websiteOptions"
+                                                :key="index"
+                                                :value="item.id + ''"
+                                                :label="item.primaryDomain"
+                                            >
+                                                <span>{{ item.primaryDomain }}</span>
+                                                <el-tag class="tagClass">
+                                                    {{ item.alias }}
+                                                </el-tag>
+                                            </el-option>
+                                        </el-select>
+                                        <span class="input-help" v-if="form.type === 'cutWebsiteLog'">
+                                            {{ $t('cronjob.cutWebsiteLogHelper') }}
+                                        </span>
+                                    </el-form-item>
+                                </el-col>
+                                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="form.type === 'app'">
+                                    <el-form-item :label="$t('cronjob.app')" prop="appID">
+                                        <el-select clearable v-model="form.appID">
+                                            <el-option
+                                                :disabled="appOptions.length === 0"
+                                                :label="$t('commons.table.all')"
+                                                value="all"
+                                            />
+                                            <div v-for="item in appOptions" :key="item.id">
+                                                <el-option :value="item.id + ''" :label="item.name">
+                                                    <span>{{ item.name }}</span>
+                                                    <el-tag class="tagClass">
+                                                        {{ item.key }}
+                                                    </el-tag>
+                                                </el-option>
+                                            </div>
+                                        </el-select>
+                                    </el-form-item>
+                                </el-col>
+                                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="form.type === 'database'">
+                                    <el-form-item :label="$t('cronjob.database')">
+                                        <el-select v-model="form.dbType" @change="loadDatabases">
+                                            <el-option label="MySQL" value="mysql" />
+                                            <el-option label="Mariadb" value="mariadb" />
+                                            <el-option label="PostgreSQL" value="postgresql" />
+                                        </el-select>
+                                    </el-form-item>
+                                </el-col>
+                                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="form.type === 'database'">
+                                    <el-form-item :label="$t('cronjob.database')" prop="dbName">
+                                        <el-select clearable v-model="form.dbName">
+                                            <el-option
+                                                :disabled="dbInfo.dbs.length === 0"
+                                                :label="$t('commons.table.all')"
+                                                value="all"
+                                            />
+                                            <el-option
+                                                v-for="item in dbInfo.dbs"
+                                                :key="item.id"
+                                                :value="item.id + ''"
+                                                :label="item.name"
+                                            >
+                                                <span>{{ item.name }}</span>
+                                                <el-tag class="tagClass">
+                                                    {{
+                                                        item.from === 'local'
+                                                            ? $t('commons.table.local')
+                                                            : $t('database.remote')
+                                                    }}
+                                                </el-tag>
+                                                <el-tag class="tagClass">
+                                                    {{ item.database }}
+                                                </el-tag>
+                                            </el-option>
+                                        </el-select>
+                                    </el-form-item>
+                                </el-col>
+                                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="form.type === 'directory'">
+                                    <el-form-item :label="$t('cronjob.backupContent')">
+                                        <el-radio-group v-model="form.isDir">
+                                            <el-radio :value="true">{{ $t('file.dir') }}</el-radio>
+                                            <el-radio :value="false">{{ $t('menu.files') }}</el-radio>
+                                        </el-radio-group>
+                                    </el-form-item>
+                                </el-col>
+                                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="form.type === 'curl'">
+                                    <el-form-item :label="$t('cronjob.url')" prop="url">
+                                        <el-input clearable v-model.trim="form.url" />
+                                    </el-form-item>
+                                </el-col>
+
+                                <div v-if="hasScript()" class="w-full">
+                                    <el-row :gutter="20">
+                                        <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
+                                            <el-form-item>
+                                                <el-checkbox v-model="form.inContainer">
+                                                    {{ $t('cronjob.containerCheckBox') }}
+                                                </el-checkbox>
+                                            </el-form-item>
+                                        </el-col>
+                                    </el-row>
+
+                                    <el-row :gutter="20" v-if="form.inContainer">
+                                        <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
+                                            <el-form-item :label="$t('cronjob.containerName')" prop="containerName">
+                                                <el-select v-model="form.containerName">
+                                                    <el-option
+                                                        v-for="item in containerOptions"
+                                                        :key="item"
+                                                        :value="item"
+                                                        :label="item"
+                                                    />
+                                                </el-select>
+                                            </el-form-item>
+                                        </el-col>
+                                        <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
+                                            <el-form-item
+                                                :label="$t('container.command')"
+                                                prop="command"
+                                                :rules="Rules.requiredInput"
+                                            >
+                                                <el-checkbox border v-model="form.isCustom">
+                                                    {{ $t('container.custom') }}
+                                                </el-checkbox>
+                                                <el-select
+                                                    v-if="!form.isCustom"
+                                                    style="width: calc(100% - 100px)"
+                                                    filterable
+                                                    clearable
+                                                    v-model="form.command"
+                                                >
+                                                    <el-option value="ash" label="/bin/ash" />
+                                                    <el-option value="bash" label="/bin/bash" />
+                                                    <el-option value="sh" label="/bin/sh" />
+                                                </el-select>
+                                                <el-input
+                                                    clearable
+                                                    v-else
+                                                    style="width: calc(100% - 100px)"
+                                                    v-model="form.command"
+                                                />
+                                            </el-form-item>
+                                        </el-col>
+                                    </el-row>
+                                    <el-row :gutter="20" v-if="!form.inContainer">
+                                        <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
+                                            <el-form-item :label="$t('commons.table.user')" prop="user">
+                                                <el-select filterable v-model="form.user">
+                                                    <div v-for="item in userOptions" :key="item">
+                                                        <el-option :value="item" :label="item" />
+                                                    </div>
+                                                </el-select>
+                                            </el-form-item>
+                                        </el-col>
+                                        <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
+                                            <el-form-item :label="$t('cronjob.executor')" prop="executor">
+                                                <el-checkbox border v-model="form.isCustom">
+                                                    {{ $t('container.custom') }}
+                                                </el-checkbox>
+                                                <el-select
+                                                    v-if="!form.isCustom"
+                                                    style="width: calc(100% - 100px)"
+                                                    v-model="form.executor"
+                                                >
+                                                    <el-option value="bash" label="bash" />
+                                                    <el-option value="python" label="python" />
+                                                    <el-option value="sh" label="sh" />
+                                                </el-select>
+                                                <el-input
+                                                    clearable
+                                                    v-else
+                                                    style="width: calc(100% - 100px)"
+                                                    v-model="form.executor"
+                                                />
+                                            </el-form-item>
+                                        </el-col>
+                                    </el-row>
+
+                                    <el-form-item :label="$t('cronjob.shellContent')" prop="script" class="mt-5">
+                                        <el-radio-group @change="form.script = ''" v-model="form.scriptMode">
+                                            <el-radio value="input">{{ $t('commons.button.edit') }}</el-radio>
+                                            <el-radio value="select">{{ $t('container.pathSelect') }}</el-radio>
+                                        </el-radio-group>
+                                    </el-form-item>
+
+                                    <CodemirrorPro
+                                        v-if="form.scriptMode === 'input'"
+                                        v-model="form.script"
+                                        placeholder="#Define or paste the content of your shell file here"
+                                        mode="javascript"
+                                        :heightDiff="400"
+                                        class="mb-5"
                                     />
-                                </el-form-item>
-                            </el-col>
-                        </el-row>
-                    </el-card>
-
-                    <el-form-item :label="$t('cronjob.shellContent')" prop="script" class="mt-5">
-                        <el-radio-group
-                            @change="dialogData.rowData!.script = ''"
-                            v-model="dialogData.rowData!.scriptMode"
-                        >
-                            <el-radio value="input">{{ $t('commons.button.edit') }}</el-radio>
-                            <el-radio value="select">{{ $t('container.pathSelect') }}</el-radio>
-                        </el-radio-group>
-                        <CodemirrorPro
-                            v-if="dialogData.rowData!.scriptMode=== 'input'"
-                            v-model="dialogData.rowData!.script"
-                            placeholder="#Define or paste the content of your shell file here"
-                            mode="javascript"
-                            :heightDiff="400"
-                        />
-                        <el-input
-                            v-if="dialogData.rowData!.scriptMode=== 'select'"
-                            :placeholder="$t('commons.example') + '/tmp/test.sh'"
-                            v-model="dialogData.rowData!.script"
-                        >
-                            <template #prepend>
-                                <FileList @choose="loadScriptDir" :dir="false"></FileList>
-                            </template>
-                        </el-input>
-                    </el-form-item>
-                </div>
-                <el-form-item :label="$t('cronjob.shellContent')" v-else prop="script" class="mt-5">
-                    <CodemirrorPro
-                        v-if="dialogData.rowData!.scriptMode=== 'input'"
-                        v-model="dialogData.rowData!.script"
-                        placeholder="#Define or paste the content of your shell file here"
-                        mode="javascript"
-                        :heightDiff="400"
-                    />
-                </el-form-item>
-            </div>
-
-            <el-form-item
-                v-if="dialogData.rowData!.type === 'website' || dialogData.rowData!.type === 'cutWebsiteLog'"
-                :label="dialogData.rowData!.type === 'website' ? $t('cronjob.website'):$t('menu.website')"
-                prop="website"
-            >
-                <el-select class="selectClass" v-model="dialogData.rowData!.website">
-                    <el-option :disabled="websiteOptions.length === 0" :label="$t('commons.table.all')" value="all" />
-                    <el-option
-                        v-for="(item, index) in websiteOptions"
-                        :key="index"
-                        :value="item.id + ''"
-                        :label="item.primaryDomain"
-                    >
-                        <span>{{ item.primaryDomain }}</span>
-                        <el-tag class="tagClass">
-                            {{ item.alias }}
-                        </el-tag>
-                    </el-option>
-                </el-select>
-                <span class="input-help" v-if="dialogData.rowData!.type === 'cutWebsiteLog'">
-                    {{ $t('cronjob.cutWebsiteLogHelper') }}
-                </span>
-            </el-form-item>
-
-            <div v-if="dialogData.rowData!.type === 'app'">
-                <el-form-item :label="$t('cronjob.app')" prop="appID">
-                    <el-select class="selectClass" clearable v-model="dialogData.rowData!.appID">
-                        <el-option :disabled="appOptions.length === 0" :label="$t('commons.table.all')" value="all" />
-                        <div v-for="item in appOptions" :key="item.id">
-                            <el-option :value="item.id + ''" :label="item.name">
-                                <span>{{ item.name }}</span>
-                                <el-tag class="tagClass">
-                                    {{ item.key }}
-                                </el-tag>
-                            </el-option>
-                        </div>
-                    </el-select>
-                </el-form-item>
-            </div>
-
-            <div v-if="dialogData.rowData!.type === 'database'">
-                <el-form-item :label="$t('cronjob.database')">
-                    <el-radio-group v-model="dialogData.rowData!.dbType" @change="loadDatabases">
-                        <el-radio value="mysql">MySQL</el-radio>
-                        <el-radio value="mariadb">Mariadb</el-radio>
-                        <el-radio value="postgresql">PostgreSQL</el-radio>
-                    </el-radio-group>
-                </el-form-item>
-                <el-form-item :label="$t('cronjob.database')" prop="dbName">
-                    <el-select class="selectClass" clearable v-model="dialogData.rowData!.dbName">
-                        <el-option :disabled="dbInfo.dbs.length === 0" :label="$t('commons.table.all')" value="all" />
-                        <el-option v-for="item in dbInfo.dbs" :key="item.id" :value="item.id + ''" :label="item.name">
-                            <span>{{ item.name }}</span>
-                            <el-tag class="tagClass">
-                                {{ item.from === 'local' ? $t('commons.table.local') : $t('database.remote') }}
-                            </el-tag>
-                            <el-tag class="tagClass">
-                                {{ item.database }}
-                            </el-tag>
-                        </el-option>
-                    </el-select>
-                </el-form-item>
-            </div>
-
-            <el-form-item v-if="dialogData.rowData!.type === 'directory'" :label="$t('cronjob.backupContent')">
-                <el-radio-group v-model="dialogData.rowData!.isDir">
-                    <el-radio :value="true">{{ $t('file.dir') }}</el-radio>
-                    <el-radio :value="false">{{ $t('menu.files') }}</el-radio>
-                </el-radio-group>
-            </el-form-item>
-
-            <el-form-item v-if="dialogData.rowData!.type === 'directory' && dialogData.rowData!.isDir" prop="sourceDir">
-                <el-input v-model="dialogData.rowData!.sourceDir">
-                    <template #prepend>
-                        <FileList @choose="loadDir" :dir="true" :path="dialogData.rowData!.sourceDir" />
-                    </template>
-                </el-input>
-            </el-form-item>
-            <div v-if="dialogData.rowData!.type === 'directory' && !dialogData.rowData!.isDir" class="mb-5">
-                <el-input>
-                    <template #prepend>
-                        <FileList @choose="loadFile" :dir="false" />
-                    </template>
-                </el-input>
-                <el-form-item prop="files">
-                    <div style="width: 100%">
-                        <ComplexTable
-                            :show-header="false"
-                            :data="dialogData.rowData.files"
-                            v-if="dialogData.rowData.files"
-                        >
-                            <el-table-column prop="val" />
-                            <el-table-column width="60">
-                                <template #default="scope">
-                                    <el-button link type="primary" @click="handleFileDelete(scope.$index)">
-                                        {{ $t('commons.button.delete') }}
-                                    </el-button>
-                                </template>
-                            </el-table-column>
-                        </ComplexTable>
-                    </div>
-                </el-form-item>
-            </div>
-
-            <div v-if="isBackup()">
-                <el-form-item :label="$t('setting.backupAccount')" prop="sourceAccountItems">
-                    <el-select
-                        multiple
-                        class="selectClass"
-                        v-model="dialogData.rowData!.sourceAccountItems"
-                        @change="changeAccount"
-                    >
-                        <div v-for="item in backupOptions" :key="item.id">
-                            <el-option v-if="item.type !== $t('setting.LOCAL')" :value="item.id" :label="item.name">
-                                {{ item.name }}
-                                <el-tag class="tagClass" type="primary">{{ item.type }}</el-tag>
-                            </el-option>
-                            <el-option v-else :value="item.id" :label="item.type" />
-                        </div>
-                    </el-select>
-                    <span class="input-help">
-                        {{ $t('cronjob.targetHelper') }}
-                        <el-link
-                            style="font-size: 12px"
-                            icon="Position"
-                            @click="goRouter('/settings/backupaccount')"
-                            type="primary"
-                        >
-                            {{ $t('firewall.quickJump') }}
-                        </el-link>
-                    </span>
-                </el-form-item>
-                <el-form-item
-                    :label="$t('setting.compressPassword')"
-                    prop="secret"
-                    v-if="isBackup() && dialogData.rowData!.type !== 'database'"
-                >
-                    <el-input v-model="dialogData.rowData!.secret" />
-                </el-form-item>
-                <el-form-item :label="$t('cronjob.default_download_path')" prop="downloadAccountID">
-                    <el-select class="selectClass" v-model="dialogData.rowData!.downloadAccountID">
-                        <div v-for="item in accountOptions" :key="item.id">
-                            <el-option v-if="item.type !== $t('setting.LOCAL')" :value="item.id" :label="item.name">
-                                {{ item.name }}
-                                <el-tag class="tagClass" type="primary">{{ item.type }}</el-tag>
-                            </el-option>
-                            <el-option v-else :value="item.id" :label="item.type" />
-                        </div>
-                    </el-select>
-                </el-form-item>
-            </div>
-
-            <div v-if="!globalStore.isIntl">
-                <el-form-item prop="hasAlert">
-                    <el-checkbox v-model="dialogData.rowData!.hasAlert" :label="$t('xpack.alert.isAlert')" />
-                    <span class="input-help">{{ $t('xpack.alert.cronJobHelper') }}</span>
-                </el-form-item>
-                <el-form-item
-                    prop="alertCount"
-                    v-if="dialogData.rowData!.hasAlert && isProductPro"
-                    :label="$t('xpack.alert.alertCount')"
-                >
-                    <el-input-number
-                        style="width: 200px"
-                        :min="1"
-                        step-strictly
-                        :step="1"
-                        v-model.number="dialogData.rowData!.alertCount"
-                    ></el-input-number>
-                    <span class="input-help">{{ $t('xpack.alert.alertCountHelper') }}</span>
-                </el-form-item>
-                <el-form-item v-if="dialogData.rowData!.hasAlert && !isProductPro">
-                    <span>{{ $t('xpack.alert.licenseHelper') }}</span>
-                    <el-button link type="primary" @click="toUpload">
-                        {{ $t('license.levelUpPro') }}
-                    </el-button>
-                </el-form-item>
-            </div>
-
-            <el-form-item :label="$t('cronjob.retainCopies')" prop="retainCopies">
-                <el-input-number
-                    style="width: 200px"
-                    :min="1"
-                    step-strictly
-                    :step="1"
-                    v-model.number="dialogData.rowData!.retainCopies"
-                ></el-input-number>
-                <span v-if="isBackup()" class="input-help">{{ $t('cronjob.retainCopiesHelper1') }}</span>
-                <span v-else class="input-help">{{ $t('cronjob.retainCopiesHelper') }}</span>
-            </el-form-item>
-
-            <el-form-item v-if="dialogData.rowData!.type === 'curl'" :label="$t('cronjob.url')" prop="url">
-                <el-input clearable v-model.trim="dialogData.rowData!.url" />
-            </el-form-item>
-
-            <el-form-item v-if="hasExclusionRules()" :label="$t('cronjob.exclusionRules')" prop="exclusionRules">
-                <el-input
-                    type="textarea"
-                    :placeholder="$t('cronjob.rulesHelper')"
-                    :rows="3"
-                    clearable
-                    v-model="dialogData.rowData!.exclusionRules"
-                />
-                <span class="input-help">{{ $t('cronjob.exclusionRulesHelper') }}</span>
-            </el-form-item>
-        </el-form>
-        <template #footer>
-            <span class="dialog-footer">
-                <el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
-                <el-button type="primary" @click="onSubmit(formRef)">
-                    {{ $t('commons.button.confirm') }}
-                </el-button>
-            </span>
+                                    <el-row :gutter="20" v-if="form.scriptMode === 'select'">
+                                        <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
+                                            <el-input
+                                                :placeholder="$t('commons.example') + '/tmp/test.sh'"
+                                                v-model="form.script"
+                                            >
+                                                <template #prepend>
+                                                    <FileList @choose="loadScriptDir" :dir="false"></FileList>
+                                                </template>
+                                            </el-input>
+                                        </el-col>
+                                    </el-row>
+                                </div>
+
+                                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="isDir() && form.isDir">
+                                    <el-form-item prop="sourceDir">
+                                        <el-input v-model="form.sourceDir">
+                                            <template #prepend>
+                                                <FileList @choose="loadDir" :dir="true" :path="form.sourceDir" />
+                                            </template>
+                                        </el-input>
+                                    </el-form-item>
+                                </el-col>
+                                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="isDir() && !form.isDir">
+                                    <el-input class="mb-5">
+                                        <template #prepend>
+                                            <FileList @choose="loadFile" :dir="false" />
+                                        </template>
+                                    </el-input>
+                                    <el-form-item prop="files">
+                                        <div style="width: 100%">
+                                            <ComplexTable :show-header="false" :data="form.files" v-if="form.files">
+                                                <el-table-column prop="val" />
+                                                <el-table-column width="60">
+                                                    <template #default="scope">
+                                                        <el-button
+                                                            link
+                                                            type="primary"
+                                                            @click="handleFileDelete(scope.$index)"
+                                                        >
+                                                            {{ $t('commons.button.delete') }}
+                                                        </el-button>
+                                                    </template>
+                                                </el-table-column>
+                                            </ComplexTable>
+                                        </div>
+                                    </el-form-item>
+                                </el-col>
+                            </el-row>
+
+                            <el-row :gutter="20">
+                                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="isBackup()">
+                                    <el-form-item :label="$t('setting.backupAccount')" prop="sourceAccountItems">
+                                        <el-select multiple v-model="form.sourceAccountItems" @change="changeAccount">
+                                            <div v-for="item in backupOptions" :key="item.id">
+                                                <el-option
+                                                    v-if="item.type !== $t('setting.LOCAL')"
+                                                    :value="item.id"
+                                                    :label="item.name"
+                                                >
+                                                    {{ item.name }}
+                                                    <el-tag class="tagClass" type="primary">{{ item.type }}</el-tag>
+                                                </el-option>
+                                                <el-option v-else :value="item.id" :label="item.type" />
+                                            </div>
+                                        </el-select>
+                                        <span class="input-help logText">
+                                            {{ $t('cronjob.targetHelper') }}
+                                            <el-link
+                                                class="link"
+                                                icon="Position"
+                                                @click="goRouter('/settings/backupaccount')"
+                                                type="primary"
+                                            >
+                                                {{ $t('firewall.quickJump') }}
+                                            </el-link>
+                                        </span>
+                                    </el-form-item>
+                                </el-col>
+                                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="isBackup()">
+                                    <el-form-item :label="$t('cronjob.default_download_path')" prop="downloadAccountID">
+                                        <el-select v-model="form.downloadAccountID">
+                                            <div v-for="item in accountOptions" :key="item.id">
+                                                <el-option
+                                                    v-if="item.type !== $t('setting.LOCAL')"
+                                                    :value="item.id"
+                                                    :label="item.name"
+                                                >
+                                                    {{ item.name }}
+                                                    <el-tag class="tagClass" type="primary">{{ item.type }}</el-tag>
+                                                </el-option>
+                                                <el-option v-else :value="item.id" :label="item.type" />
+                                            </div>
+                                        </el-select>
+                                    </el-form-item>
+                                </el-col>
+                            </el-row>
+
+                            <el-row :gutter="20">
+                                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" v-if="isBackup() && !isDatabase()">
+                                    <el-form-item :label="$t('setting.compressPassword')" prop="secret">
+                                        <el-input v-model="form.secret" />
+                                    </el-form-item>
+                                </el-col>
+                                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
+                                    <el-form-item :label="$t('cronjob.retainCopies')" prop="retainCopies">
+                                        <el-input-number
+                                            class="selectClass"
+                                            :min="1"
+                                            step-strictly
+                                            :step="1"
+                                            v-model.number="form.retainCopies"
+                                        ></el-input-number>
+                                        <span v-if="isBackup()" class="input-help">
+                                            {{ $t('cronjob.retainCopiesHelper1') }}
+                                        </span>
+                                        <span v-else class="input-help">{{ $t('cronjob.retainCopiesHelper') }}</span>
+                                    </el-form-item>
+                                </el-col>
+                            </el-row>
+                            <el-row :gutter="20">
+                                <el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="20" v-if="hasExclusionRules()">
+                                    <el-form-item :label="$t('cronjob.exclusionRules')" prop="exclusionRules">
+                                        <el-input
+                                            :placeholder="$t('cronjob.rulesHelper')"
+                                            clearable
+                                            v-model="form.exclusionRules"
+                                        />
+                                        <span class="input-help">{{ $t('cronjob.exclusionRulesHelper') }}</span>
+                                    </el-form-item>
+                                </el-col>
+                            </el-row>
+                        </el-card>
+
+                        <el-card class="mt-5">
+                            <div v-if="!globalStore.isIntl">
+                                <el-row :gutter="20">
+                                    <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
+                                        <el-form-item prop="hasAlert">
+                                            <el-checkbox v-model="form.hasAlert" :label="$t('xpack.alert.isAlert')" />
+                                            <span class="input-help">{{ $t('xpack.alert.cronJobHelper') }}</span>
+
+                                            <span class="input-help logText" v-if="form.hasAlert && !isProductPro">
+                                                {{ $t('xpack.alert.licenseHelper') }}
+                                                <el-link class="link" @click="toUpload" type="primary">
+                                                    {{ $t('license.levelUpPro') }}
+                                                </el-link>
+                                            </span>
+                                        </el-form-item>
+                                    </el-col>
+                                    <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
+                                        <el-form-item
+                                            prop="alertCount"
+                                            v-if="form.hasAlert && isProductPro"
+                                            :label="$t('xpack.alert.alertCount')"
+                                        >
+                                            <el-input-number
+                                                class="selectClass"
+                                                :min="1"
+                                                step-strictly
+                                                :step="1"
+                                                v-model.number="form.alertCount"
+                                            ></el-input-number>
+                                            <span class="input-help">{{ $t('xpack.alert.alertCountHelper') }}</span>
+                                        </el-form-item>
+                                    </el-col>
+                                </el-row>
+                            </div>
+
+                            <el-row :gutter="20">
+                                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
+                                    <el-form-item :label="$t('cronjob.timeout')" prop="timeoutItem">
+                                        <el-input type="number" class="selectClass" v-model.number="form.timeoutItem">
+                                            <template #append>
+                                                <el-select v-model="form.timeoutUint" style="width: 80px">
+                                                    <el-option :label="$t('commons.units.second')" value="s" />
+                                                    <el-option :label="$t('commons.units.minute')" value="m" />
+                                                    <el-option :label="$t('commons.units.hour')" value="h" />
+                                                </el-select>
+                                            </template>
+                                        </el-input>
+                                    </el-form-item>
+                                </el-col>
+                                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
+                                    <el-form-item :label="$t('cronjob.retryTimes')" prop="retryTimes">
+                                        <el-input-number
+                                            class="selectClass"
+                                            :min="0"
+                                            step-strictly
+                                            :step="1"
+                                            v-model.number="form.retryTimes"
+                                        ></el-input-number>
+                                        <span class="input-help">{{ $t('cronjob.retryTimesHelper') }}</span>
+                                    </el-form-item>
+                                </el-col>
+                            </el-row>
+                        </el-card>
+
+                        <el-form-item class="mt-5">
+                            <el-button :disabled="loading" @click="goBack">
+                                {{ $t('commons.button.back') }}
+                            </el-button>
+                            <el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
+                                {{ $t('commons.button.confirm') }}
+                            </el-button>
+                        </el-form-item>
+                    </el-col>
+                </el-row>
+            </el-form>
         </template>
-        <LicenseImport ref="licenseRef" />
-    </DrawerPro>
+    </LayoutContent>
+
+    <LicenseImport ref="licenseRef" />
 </template>
 
 <script lang="ts" setup>
@@ -533,7 +638,7 @@ import { listBackupOptions } from '@/api/modules/backup';
 import i18n from '@/lang';
 import { ElForm } from 'element-plus';
 import { Cronjob } from '@/api/interface/cronjob';
-import { addCronjob, editCronjob, loadNextHandle } from '@/api/modules/cronjob';
+import { addCronjob, editCronjob, loadCronjobInfo, loadNextHandle } from '@/api/modules/cronjob';
 import CodemirrorPro from '@/components/codemirror-pro/index.vue';
 import { listDbItems } from '@/api/modules/database';
 import { getWebsiteOptions } from '@/api/modules/website';
@@ -549,110 +654,165 @@ import {
     transObjToSpec,
     transSpecToObj,
     weekOptions,
-} from './../helper';
+} from '../helper';
 import { loadUsers } from '@/api/modules/toolbox';
 import { storeToRefs } from 'pinia';
 import { GlobalStore } from '@/store';
 import LicenseImport from '@/components/license-import/index.vue';
+import { transferTimeToSecond } from '@/utils/util';
 const router = useRouter();
 
 const globalStore = GlobalStore();
 const licenseRef = ref();
 const { isProductPro } = storeToRefs(globalStore);
+const loading = ref();
+const nextTimes = ref([]);
 
-interface DialogProps {
-    title: string;
-    rowData?: Cronjob.CronjobInfo;
-    getTableList?: () => Promise<any>;
-}
-const title = ref<string>('');
-const drawerVisible = ref(false);
-const dialogData = ref<DialogProps>({
-    title: '',
+const isCreate = ref();
+const form = reactive<Cronjob.CronjobInfo>({
+    id: 0,
+    name: '',
+    type: 'shell',
+    specCustom: false,
+    spec: '',
+    specs: [],
+    specObjs: [{ specType: 'perMonth', week: 1, day: 3, hour: 1, minute: 30, second: 30 }],
+
+    executor: '',
+    isExecutorCustom: false,
+    script: '',
+    scriptMode: 'input',
+    isCustom: false,
+    command: '',
+    inContainer: false,
+    containerName: '',
+    user: '',
+    appID: '',
+    website: '',
+    exclusionRules: '',
+    dbType: 'mysql',
+    dbName: '',
+    url: '',
+    isDir: true,
+    files: [],
+    sourceDir: '',
+
+    sourceAccounts: [],
+    downloadAccount: '',
+    sourceAccountIDs: '',
+    downloadAccountID: 0,
+    sourceAccountItems: [],
+
+    retainCopies: 7,
+    retryTimes: 0,
+    timeout: 300,
+    timeoutItem: 300,
+    timeoutUint: 's',
+    status: '',
+    secret: '',
+    hasAlert: false,
+    alertCount: 0,
+    alertTitle: '',
 });
-const nextTimes = ref([]);
 
-const acceptParams = (params: DialogProps): void => {
-    dialogData.value = params;
-    if (!dialogData.value.rowData?.specCustom && dialogData.value.rowData?.spec) {
-        let objs = [];
-        for (const item of dialogData.value.rowData.spec.split(',')) {
-            objs.push(transSpecToObj(item));
-        }
-        dialogData.value.rowData.specObjs = objs;
-    }
-    dialogData.value.rowData.specObjs = dialogData.value.rowData.specObjs || [];
-    if (dialogData.value.rowData?.specCustom && dialogData.value.rowData?.spec) {
-        dialogData.value.rowData.specs = dialogData.value.rowData.spec.split(',');
-    }
-    if (dialogData.value.rowData.sourceAccountIDs) {
-        let list = [];
-        dialogData.value.rowData.sourceAccountItems = [];
-        list = dialogData.value.rowData.sourceAccountIDs.split(',');
-        for (const item of list) {
-            if (item) {
-                dialogData.value.rowData.sourceAccountItems.push(Number(item));
-            }
-        }
-    }
-    dialogData.value.rowData.specs = dialogData.value.rowData.specs || [];
-    dialogData.value.rowData.files = [];
-    if (!dialogData.value.rowData.isDir) {
-        let files = dialogData.value.rowData.sourceDir?.split(',') || [];
-        for (const item of files) {
-            dialogData.value.rowData.files.push({ val: item });
-        }
-    }
-    if (dialogData.value.title === 'create') {
-        changeType();
-        dialogData.value.rowData.scriptMode = 'input';
-        dialogData.value.rowData.dbType = 'mysql';
-        dialogData.value.rowData.isDir = true;
-    }
-    dialogData.value.rowData.hasAlert = dialogData.value.rowData!.alertCount > 0;
-    dialogData.value.rowData!.alertCount = dialogData.value.rowData!.alertCount || 3;
-    dialogData.value.rowData!.command = dialogData.value.rowData!.command || 'sh';
-    dialogData.value.rowData!.isCustom =
-        dialogData.value.rowData!.command !== 'sh' &&
-        dialogData.value.rowData!.command !== 'bash' &&
-        dialogData.value.rowData!.command !== 'ash';
-
-    dialogData.value.rowData!.executor = dialogData.value.rowData!.executor || 'bash';
-    dialogData.value.rowData!.isCustom =
-        dialogData.value.rowData!.executor !== 'sh' &&
-        dialogData.value.rowData!.executor !== 'bash' &&
-        dialogData.value.rowData!.executor !== 'python' &&
-        dialogData.value.rowData!.executor !== 'python3';
-
-    title.value = i18n.global.t('cronjob.' + dialogData.value.title);
-    if (dialogData.value?.rowData?.exclusionRules) {
-        dialogData.value.rowData.exclusionRules = dialogData.value.rowData.exclusionRules.replaceAll(',', '\n');
-    }
-    if (dialogData.value?.rowData?.containerName) {
-        dialogData.value.rowData.inContainer = true;
+const search = async () => {
+    if (!isCreate.value) {
+        loading.value = true;
+        await loadCronjobInfo(form.id)
+            .then((res) => {
+                loading.value = false;
+                form.name = res.data.name;
+                form.type = res.data.type;
+                form.specCustom = res.data.specCustom;
+                form.spec = res.data.spec;
+                form.specs = res.data.specs;
+                if (!form.specCustom && form.spec) {
+                    let objs = [];
+                    for (const item of res.data.spec.split(',')) {
+                        objs.push(transSpecToObj(item));
+                    }
+                    form.specObjs = objs || [];
+                }
+                if (form.specCustom && form.spec) {
+                    form.specs = form.spec.split(',') || [];
+                }
+
+                form.script = res.data.script;
+                form.scriptMode = res.data.scriptMode;
+
+                form.containerName = res.data.containerName;
+                if (form.containerName.length !== 0) {
+                    form.inContainer = true;
+                    form.command = res.data.command || 'sh';
+                    form.isCustom = form.command !== 'sh' && form.command !== 'bash' && form.command !== 'ash';
+                } else {
+                    form.executor = res.data.executor || 'bash';
+                    form.user = res.data.user;
+                    form.isCustom =
+                        form.executor !== 'sh' &&
+                        form.executor !== 'bash' &&
+                        form.executor !== 'python' &&
+                        form.executor !== 'python3';
+                }
+
+                form.appID = res.data.appID;
+                form.website = res.data.website;
+                form.exclusionRules = res.data.exclusionRules;
+                form.dbType = res.data.dbType;
+                form.dbName = res.data.dbName;
+                form.url = res.data.url;
+
+                form.isDir = res.data.isDir;
+                form.sourceDir = res.data.sourceDir;
+                if (!form.isDir) {
+                    let files = form.sourceDir?.split(',') || [];
+                    for (const item of files) {
+                        form.files.push({ val: item });
+                    }
+                }
+
+                form.sourceAccountIDs = res.data.sourceAccountIDs;
+                form.downloadAccountID = res.data.downloadAccountID;
+                if (form.sourceAccountIDs) {
+                    let list = [];
+                    form.sourceAccountItems = [];
+                    list = form.sourceAccountIDs.split(',');
+                    for (const item of list) {
+                        if (item) {
+                            form.sourceAccountItems.push(Number(item));
+                        }
+                    }
+                }
+
+                form.retainCopies = res.data.retainCopies;
+                form.retryTimes = res.data.retryTimes;
+                form.timeout = res.data.timeout;
+                form.timeoutItem = res.data.timeout || 300;
+                form.secret = res.data.secret;
+                form.hasAlert = res.data.alertCount > 0;
+                form.alertCount = res.data.alertCount || 3;
+                form.alertTitle = res.data.alertTitle;
+            })
+            .catch(() => {
+                loading.value = false;
+            });
     }
-    drawerVisible.value = true;
     loadBackups();
     loadAppInstalls();
     loadShellUsers();
     loadWebsites();
     loadContainers();
-    if (dialogData.value.rowData?.dbType) {
-        loadDatabases(dialogData.value.rowData.dbType);
+    if (form.dbType) {
+        loadDatabases(form.dbType);
     } else {
         loadDatabases('mysql');
     }
 };
-const emit = defineEmits<{ (e: 'search'): void }>();
 
 const goRouter = async (path: string) => {
     router.push({ path: path });
 };
 
-const handleClose = () => {
-    drawerVisible.value = false;
-};
-
 const containerOptions = ref([]);
 const websiteOptions = ref([]);
 const backupOptions = ref([]);
@@ -668,7 +828,7 @@ const dbInfo = reactive({
 });
 
 const verifyScript = (rule: any, value: any, callback: any) => {
-    if (!dialogData.value.rowData!.script || dialogData.value.rowData!.script.length === 0) {
+    if (!form.script || form.script.length === 0) {
         callback(new Error(i18n.global.t('commons.rule.requiredInput')));
         return;
     }
@@ -676,13 +836,13 @@ const verifyScript = (rule: any, value: any, callback: any) => {
 };
 
 const verifySpec = (rule: any, value: any, callback: any) => {
-    if (dialogData.value.rowData!.specCustom) {
-        if (dialogData.value.rowData!.specs.length === 0) {
+    if (form.specCustom) {
+        if (form.specs.length === 0) {
             callback(new Error(i18n.global.t('commons.rule.requiredInput')));
             return;
         }
-        for (let i = 0; i < dialogData.value.rowData!.specs.length; i++) {
-            if (dialogData.value.rowData!.specs[i]) {
+        for (let i = 0; i < form.specs.length; i++) {
+            if (form.specs[i]) {
                 continue;
             }
             callback(new Error(i18n.global.t('cronjob.cronSpecRule', [i + 1])));
@@ -691,11 +851,11 @@ const verifySpec = (rule: any, value: any, callback: any) => {
         callback();
         return;
     }
-    if (dialogData.value.rowData!.specObjs.length === 0) {
+    if (form.specObjs.length === 0) {
         callback(new Error(i18n.global.t('commons.rule.requiredInput')));
     }
-    for (let i = 0; i < dialogData.value.rowData!.specObjs.length; i++) {
-        let item = dialogData.value.rowData!.specObjs[i];
+    for (let i = 0; i < form.specObjs.length; i++) {
+        let item = form.specObjs[i];
         if (
             !Number.isInteger(item.day) ||
             !Number.isInteger(item.hour) ||
@@ -781,7 +941,7 @@ const verifySpec = (rule: any, value: any, callback: any) => {
 };
 
 const verifyFiles = (rule: any, value: any, callback: any) => {
-    if (!dialogData.value.rowData!.files || dialogData.value.rowData!.files.length === 0) {
+    if (!form.files || form.files.length === 0) {
         callback(new Error(i18n.global.t('commons.rule.requiredInput')));
         return;
     }
@@ -818,6 +978,9 @@ const rules = reactive({
     sourceAccountItems: [Rules.requiredSelect],
     downloadAccountID: [Rules.requiredSelect],
     retainCopies: [Rules.number],
+    retryTimes: [Rules.number],
+    timeoutItem: [Rules.number],
+    timeoutUint: [Rules.requiredSelect],
     alertCount: [Rules.integerNumber, { validator: checkSendCount, trigger: 'blur' }],
 });
 
@@ -825,20 +988,24 @@ type FormInstance = InstanceType<typeof ElForm>;
 const formRef = ref<FormInstance>();
 
 const loadDir = async (path: string) => {
-    dialogData.value.rowData!.sourceDir = path;
+    form.sourceDir = path;
 };
 
 const loadScriptDir = async (path: string) => {
-    dialogData.value.rowData!.script = path;
+    form.script = path;
+};
+
+const goBack = () => {
+    router.push({ name: 'CronjobItem' });
 };
 
 const loadFile = async (path: string) => {
-    for (const item of dialogData.value.rowData!.files) {
+    for (const item of form.files) {
         if (item.val === path) {
             return;
         }
     }
-    dialogData.value.rowData!.files.push({ val: path });
+    form.files.push({ val: path });
 };
 
 const hasDay = (item: any) => {
@@ -847,11 +1014,20 @@ const hasDay = (item: any) => {
 const hasHour = (item: any) => {
     return item.specType !== 'perHour' && item.specType !== 'perNMinute' && item.specType !== 'perNSecond';
 };
+const isWebsite = () => {
+    return form.type === 'website' || form.type === 'cutWebsiteLog';
+};
+const isDir = () => {
+    return form.type === 'directory';
+};
+const isDatabase = () => {
+    return form.type === 'database';
+};
 
 const loadNext = async (spec: any) => {
     nextTimes.value = [];
     let specItem = '';
-    if (!dialogData.value.rowData.specCustom) {
+    if (!form.specCustom) {
         specItem = transObjToSpec(spec.specType, spec.week, spec.day, spec.hour, spec.minute, spec.second);
     } else {
         specItem = spec;
@@ -870,12 +1046,12 @@ const loadDatabases = async (dbType: string) => {
 };
 
 const changeType = () => {
-    dialogData.value.rowData!.specObjs = [loadDefaultSpec(dialogData.value.rowData.type)];
-    dialogData.value.rowData!.specs = [loadDefaultSpecCustom(dialogData.value.rowData.type)];
+    form.specObjs = [loadDefaultSpec(form.type)];
+    form.specs = [loadDefaultSpecCustom(form.type)];
 };
 
 const changeSpecType = (index: number) => {
-    let item = dialogData.value.rowData!.specObjs[index];
+    let item = form.specObjs[index];
     switch (item.specType) {
         case 'perMonth':
         case 'perNDay':
@@ -912,23 +1088,23 @@ const handleSpecAdd = () => {
         minute: 30,
         second: 0,
     };
-    dialogData.value.rowData!.specObjs.push(item);
+    form.specObjs.push(item);
 };
 
 const handleSpecCustomAdd = () => {
-    dialogData.value.rowData!.specs.push('');
+    form.specs.push('');
 };
 
 const handleSpecDelete = (index: number) => {
-    dialogData.value.rowData!.specObjs.splice(index, 1);
+    form.specObjs.splice(index, 1);
 };
 
 const handleSpecCustomDelete = (index: number) => {
-    dialogData.value.rowData!.specs.splice(index, 1);
+    form.specs.splice(index, 1);
 };
 
 const handleFileDelete = (index: number) => {
-    dialogData.value.rowData!.files.splice(index, 1);
+    form.files.splice(index, 1);
 };
 
 const loadBackups = async () => {
@@ -945,8 +1121,8 @@ const loadBackups = async () => {
         }
         backupOptions.value.push({ id: item.id, type: i18n.global.t('setting.' + item.type), name: item.name });
     }
-    if (!dialogData.value.rowData!.sourceAccountItems) {
-        dialogData.value.rowData!.sourceAccountItems = local === 0 ? [local] : [];
+    if (!form.sourceAccountItems) {
+        form.sourceAccountItems = local === 0 ? [local] : [];
     }
     changeAccount();
 };
@@ -956,21 +1132,21 @@ const changeAccount = async () => {
     let isInAccounts = false;
     for (const item of backupOptions.value) {
         let exist = false;
-        for (const ac of dialogData.value.rowData.sourceAccountItems) {
+        for (const ac of form.sourceAccountItems) {
             if (item.id == ac) {
                 exist = true;
                 break;
             }
         }
         if (exist) {
-            if (item.id === dialogData.value.rowData.downloadAccountID) {
+            if (item.id === form.downloadAccountID) {
                 isInAccounts = true;
             }
             accountOptions.value.push(item);
         }
     }
     if (!isInAccounts) {
-        dialogData.value.rowData.downloadAccountID = undefined;
+        form.downloadAccountID = undefined;
     }
 };
 
@@ -996,31 +1172,27 @@ const loadContainers = async () => {
 
 function isBackup() {
     return (
-        dialogData.value.rowData!.type === 'app' ||
-        dialogData.value.rowData!.type === 'website' ||
-        dialogData.value.rowData!.type === 'database' ||
-        dialogData.value.rowData!.type === 'directory' ||
-        dialogData.value.rowData!.type === 'snapshot' ||
-        dialogData.value.rowData!.type === 'log'
+        form.type === 'app' ||
+        form.type === 'website' ||
+        form.type === 'database' ||
+        form.type === 'directory' ||
+        form.type === 'snapshot' ||
+        form.type === 'log'
     );
 }
 
 function hasExclusionRules() {
-    return (
-        dialogData.value.rowData!.type === 'app' ||
-        dialogData.value.rowData!.type === 'website' ||
-        (dialogData.value.rowData!.type === 'directory' && dialogData.value.rowData!.isDir)
-    );
+    return form.type === 'app' || form.type === 'website' || (form.type === 'directory' && form.isDir);
 }
 
 function hasScript() {
-    return dialogData.value.rowData!.type === 'shell';
+    return form.type === 'shell';
 }
 
 const onSubmit = async (formEl: FormInstance | undefined) => {
     let specs = [];
-    if (!dialogData.value.rowData.specCustom) {
-        for (const item of dialogData.value.rowData.specObjs) {
+    if (!form.specCustom) {
+        for (const item of form.specObjs) {
             const itemSpec = transObjToSpec(item.specType, item.week, item.day, item.hour, item.minute, item.second);
             if (itemSpec === '') {
                 MsgError(i18n.global.t('cronjob.cronSpecHelper'));
@@ -1029,48 +1201,40 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
             specs.push(itemSpec);
         }
     } else {
-        specs = dialogData.value.rowData.specs;
+        specs = form.specs;
     }
-    if (!dialogData.value.rowData.isDir) {
+    if (!form.isDir) {
         let files = [];
-        for (const item of dialogData.value.rowData.files) {
+        for (const item of form.files) {
             files.push(item.val);
         }
-        dialogData.value.rowData.sourceDir = files.join(',');
+        form.sourceDir = files.join(',');
     }
-    dialogData.value.rowData.sourceAccountIDs = dialogData.value.rowData.sourceAccountItems.join(',');
-    dialogData.value.rowData.spec = specs.join(',');
+    form.sourceAccountIDs = form.sourceAccountItems.join(',');
+    form.spec = specs.join(',');
     if (!formEl) return;
     formEl.validate(async (valid) => {
         if (!valid) return;
-        if (!dialogData.value.rowData.inContainer) {
-            dialogData.value.rowData.containerName = '';
-        }
-        if (dialogData.value?.rowData?.exclusionRules) {
-            dialogData.value.rowData.exclusionRules = dialogData.value.rowData.exclusionRules.replaceAll('\n', ',');
+        if (!form.inContainer) {
+            form.containerName = '';
         }
+        form.timeout = transferTimeToSecond(form.timeoutItem + form.timeoutUint);
 
-        dialogData.value.rowData.alertCount =
-            dialogData.value.rowData!.hasAlert && isProductPro.value ? dialogData.value.rowData.alertCount : 0;
-        dialogData.value.rowData.alertTitle =
-            dialogData.value.rowData!.hasAlert && isProductPro.value
-                ? i18n.global.t('cronjob.alertTitle', [
-                      i18n.global.t('cronjob.' + dialogData.value.rowData.type),
-                      dialogData.value.rowData.name,
-                  ])
+        form.alertCount = form.hasAlert && isProductPro.value ? form.alertCount : 0;
+        form.alertTitle =
+            form.hasAlert && isProductPro.value
+                ? i18n.global.t('cronjob.alertTitle', [i18n.global.t('cronjob.' + form.type), form.name])
                 : '';
-        if (!dialogData.value.rowData) return;
+        if (!form) return;
 
-        if (dialogData.value.title === 'create') {
-            await addCronjob(dialogData.value.rowData);
-        }
-        if (dialogData.value.title === 'edit') {
-            await editCronjob(dialogData.value.rowData);
+        if (isCreate.value) {
+            await addCronjob(form);
+        } else {
+            await editCronjob(form);
         }
 
         MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
-        emit('search');
-        drawerVisible.value = false;
+        goBack();
     });
 };
 
@@ -1078,8 +1242,14 @@ const toUpload = () => {
     licenseRef.value.acceptParams();
 };
 
-defineExpose({
-    acceptParams,
+onMounted(() => {
+    if (router.currentRoute.value.query.id) {
+        isCreate.value = false;
+        form.id = Number(router.currentRoute.value.query.id);
+    } else {
+        isCreate.value = true;
+    }
+    search();
 });
 </script>
 <style scoped lang="scss">
@@ -1129,7 +1299,7 @@ defineExpose({
     line-height: 22px;
     font-size: 12px;
     .link {
-        font-size: 12px;
+        font-size: 12px !important;
         margin-top: -3px;
     }
 }


Reviews:
user: login: wanghe-fit2cloud
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T02:26:24Z
user: login: wanghe-fit2cloud
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T02:26:32Z
user: login: wanghe-fit2cloud
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T02:26:49Z
user: login: wanghe-fit2cloud
body: /lgtm
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T05:58:11Z

Review comments:
diff_hunk: @@ -73,24 +93,6 @@ export namespace Cronjob {
         minute: number;
         second: number;
     }
-    export interface CronjobUpdate {
-        id: number;
-        specCustom: boolean;
-        spec: string;
-
-        script: string;
-        website: string;
-        exclusionRules: string;
-        dbType: string;
-        dbName: string;
-        url: string;
-        sourceDir: string;
-
-        sourceAccountIDs: string;
-        downloadAccountID: number;
-        retainCopies: number;
-        secret: string;
-    }
     export interface CronjobDelete {
         ids: Array<number>;
         cleanData: boolean;
path: frontend/src/api/interface/cronjob.ts
user: login: wanghe-fit2cloud
body: There are several issues with this code:

1. The addition of new fields (retryTimes, timeout, etc.) to both `Item` and `CronjobCreate/CronjobOperate` interfaces without updating corresponding types where these fields should be used can lead to compile errors when using the updated interfaces.

2. The `id` field is duplicated in some interfaces (`Cronjob`, `CronjobCreat`, `CronjobOperate`). This inconsistency might cause confusion or bugs if not handled properly.

3. Missing documentation or comments on the newly added fields can make it harder to understand their purpose and usage.

4. There seems to be an indentation issue near "website" under `SourceItems`.

To resolve these issues:
- Ensure all references to 'item.id' use the correct ID field from appropriate contexts.
- Remove duplicate 'id' fields across different interfaces.
- Add necessary comments above each new field explaining its purpose and functionality.
- Correct the indentation around "website" under "SourceAccountItems".

Optimization suggestions could include checking whether `sourceAccountIDs` and `downloadAccountID` support more complex data structures and provide functions for easily manipulating them instead of relying solely on string representations. Additionally, considering adding error handling for null/undefined value checks before operations that rely on them would enhance robustness.
created_at: 2025-04-16T02:26:24Z
author_association: MEMBER
diff_hunk: @@ -48,40 +49,6 @@ type CronjobCreate struct {
 	AlertTitle string `json:"alertTitle"`
 }
 
-type CronjobUpdate struct {
-	ID         uint   `json:"id" validate:"required"`
-	Type       string `json:"type" validate:"required"`
-	Name       string `json:"name" validate:"required"`
-	SpecCustom bool   `json:"specCustom"`
-	Spec       string `json:"spec" validate:"required"`
-
-	Executor      string `json:"executor"`
-	ScriptMode    string `json:"scriptMode"`
-	Script        string `json:"script"`
-	Command       string `json:"command"`
-	ContainerName string `json:"containerName"`
-	User          string `json:"user"`
-
-	AppID          string `json:"appID"`
-	Website        string `json:"website"`
-	ExclusionRules string `json:"exclusionRules"`
-	DBType         string `json:"dbType"`
-	DBName         string `json:"dbName"`
-	URL            string `json:"url"`
-	IsDir          bool   `json:"isDir"`
-	SourceDir      string `json:"sourceDir"`
-
-	SourceAccountIDs  string `json:"sourceAccountIDs"`
-	DownloadAccountID uint   `json:"downloadAccountID"`
-	RetainCopies      int    `json:"retainCopies" validate:"number,min=1"`
-	RetryTimes        int    `json:"retryTimes" validate:"number,min=0"`
-	Timeout           uint   `json:"timeout" validate:"number,min=1"`
-	Secret            string `json:"secret"`
-
-	AlertCount uint   `json:"alertCount"`
-	AlertTitle string `json:"alertTitle"`
-}
-
 type CronjobUpdateStatus struct {
 	ID     uint   `json:"id" validate:"required"`
 	Status string `json:"status" validate:"required"`
path: agent/app/dto/cronjob.go
user: login: wanghe-fit2cloud
body: The changes include:

1. **Type Renaming**: The struct `CronjobCreate` has been renamed to `CronjobOperate`. This is beneficial because it clearly indicates that this struct is used for operations rather than creating a new cron job.

2. **Addition of Missing Fields**:
   - Added `ID`, which can be useful for identifying specific cron jobs when performing updates.
   - Added fields like `Command`, `ContainerName`, `User`, etc., based on the previous structs for clarity and completeness. These should be validated appropriately according to their types.

3. **Optimization Suggestions**:
   
   - **Consistency in Field Names**: Ensure consistency between field names across all related structs (`CronjobCreate`, `CronjobUpdate`) if applicable, such as using common prefixes like `App_`, `Website_`, etc., instead of separate fields.
   - **Validation Annotations**: While validation annotations help maintain data integrity, ensure they do not conflict with each other or are overly restrictive (e.g., making fields required unnecessarily).
   - **Comments**: Add comments where necessary to explain complex logic or sections of code, especially important in areas involving script/custom execution paths.

Overall, these changes enhance readability and functionality while providing a clear structure for handling various operations related to cron jobs.
created_at: 2025-04-16T02:26:32Z
author_association: MEMBER
diff_hunk: @@ -362,7 +340,7 @@ const buttons = [
     {
         label: i18n.global.t('commons.button.edit'),
         click: (row: Cronjob.CronjobInfo) => {
-            onOpenDialog('edit', row);
+            onOpenDialog(row.id + '');
         },
     },
     {
path: frontend/src/views/cronjob/cronjob/index.vue
user: login: wanghe-fit2cloud
body: There are several improvements and corrections in the provided code:

1. **Remove Unused Code**: The `OperateDialog` component is unused and should be removed from the template.

2. **Consistent Use of Variables**: Ensure consistent use of variable names throughout the script to avoid potential bugs or confusion.

3. **Routing Correction**: The `onOpenDialog` function now navigates using Vue Router rather than creating an operation dialog directly.

4. **Code Formatting**: Improve line spacing and consistency to enhance readability.

Here's the revised code with these changes:

```html
<template>
    <div>
        <LayoutContent v-loading="loading" v-if="!isRecordShow" :title="$t('menu.cronjob')">
            <template #leftToolBar>
                <el-button type="primary" @click="onOpenDialog('create')">
                    {{ $t('commons.button.create') }}{{ $t('menu.cronjob') }}
                </el-button>
                <el-button-group class="ml-4">
                <!-- Other left toolbar elements -->
                </el-button-group>
            </template>
        </LayoutContent>

        <Records @search="search" ref="dialogRecordRef" />
        <Backups @search="search" ref="dialogBackupRef" />
    </div>
</template>

<script lang="ts" setup>
import Records from '@/views/cronjob/cronjob/record/index.vue';
import Backups from '@/views/cronjob/cronjob/backup/index.vue';
import { computed, onMounted, reactive, ref } from 'vue';
import { ElMessageBox } from 'element-plus';
import { MsgSuccess } from '@/utils/message';
import { transSpecToStr } from './helper';
import { GlobalStore } from '@/store';

const globalStore = GlobalStore();
const mobile = computed(() => {
  // Compute logic here if needed
});

const loading = ref(false);
const isRecordShow = ref(true); // Assuming this is set elsewhere

// Functionality for searching
const search = async (column?: any) => {
  // Handle search functionality
};

const dialogRecordRef = ref();
const dialogBackupRef = ref();

// New function for opening dialogs based on ID
const onOpenDialog = async (id: string) => {
  router.push({ name: 'CronjobOperate', query: { id: id } });
};

const onDelete = async (row: Cronjob.CronjobInfo | null) => {
  // Implement delete functionality
};

const buttons = [
  {
    label: i18n.global.t('commons.button.edit'),
    click: (row: Cronjob.CronjobInfo) => {
      onOpenDialog(row.id + '');
    },
  },
  {
    // Other button configurations
  },
];
</script>
```

These changes address the identified issues and improve maintainability and structure of the code.
created_at: 2025-04-16T02:26:49Z
author_association: MEMBER

PR comments:
user: login: f2c-ci-robot[bot]
created_at: 2025-04-16T02:26:08Z
body: Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our [release note process](https://git.k8s.io/community/contributors/guide/release-notes.md) to remove it.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-04-16T02:27:37Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=1Panel-dev_1Panel&pullRequest=8403) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [4 New issues](https://sonarcloud.io/project/issues?id=1Panel-dev_1Panel&pullRequest=8403&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=1Panel-dev_1Panel&pullRequest=8403&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=1Panel-dev_1Panel&pullRequest=8403&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=1Panel-dev_1Panel&pullRequest=8403&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1.7% Duplication on New Code](https://sonarcloud.io/component_measures?id=1Panel-dev_1Panel&pullRequest=8403&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=1Panel-dev_1Panel&pullRequest=8403)


author_association: NONE
user: login: wanghe-fit2cloud
created_at: 2025-04-16T05:58:20Z
body: /approve
author_association: MEMBER
user: login: f2c-ci-robot[bot]
created_at: 2025-04-16T05:58:27Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/1Panel-dev/1Panel/pull/8403#issuecomment-2808457725" title="Approved">wanghe-fit2cloud</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=1Panel-dev%2F1Panel).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/1Panel-dev/1Panel/blob/dev-v2/OWNERS)~~ [wanghe-fit2cloud]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: NONE