From 63ae17372dc8860d47fd9cc3371e6e8d1c4114bc Mon Sep 17 00:00:00 2001 From: ssongliu <73214554+ssongliu@users.noreply.github.com> Date: Wed, 23 Aug 2023 22:44:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AE=A1=E5=88=92=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=A4=87=E4=BB=BD=E5=BA=94=E7=94=A8=20(#2040?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #1788 #1713 --- backend/app/api/v1/app_install.go | 20 ++++- backend/app/dto/app.go | 6 ++ backend/app/dto/cronjob.go | 3 + backend/app/model/cronjob.go | 1 + backend/app/service/app_install.go | 17 +++- backend/app/service/cornjob.go | 4 +- backend/app/service/cronjob_helper.go | 87 +++++++++++++++++++- backend/init/migration/migrations/init.go | 4 +- backend/router/ro_app.go | 1 + cmd/server/docs/docs.go | 52 +++++++++++- cmd/server/docs/swagger.json | 52 +++++++++++- cmd/server/docs/swagger.yaml | 34 +++++++- frontend/src/api/interface/app.ts | 6 ++ frontend/src/api/interface/cronjob.ts | 1 + frontend/src/api/modules/app.ts | 4 + frontend/src/lang/modules/en.ts | 1 + frontend/src/lang/modules/tw.ts | 1 + frontend/src/lang/modules/zh.ts | 1 + frontend/src/views/cronjob/operate/index.vue | 26 ++++++ frontend/src/views/cronjob/record/index.vue | 32 ++++++- 20 files changed, 332 insertions(+), 21 deletions(-) diff --git a/backend/app/api/v1/app_install.go b/backend/app/api/v1/app_install.go index 29165893a..dd8256143 100644 --- a/backend/app/api/v1/app_install.go +++ b/backend/app/api/v1/app_install.go @@ -14,8 +14,8 @@ import ( ) // @Tags App -// @Summary List app installed -// @Description 获取已安装应用列表 +// @Summary Page app installed +// @Description 分页获取已安装应用列表 // @Accept json // @Param request body request.AppInstalledSearch true "request" // @Success 200 @@ -47,6 +47,22 @@ func (b *BaseApi) SearchAppInstalled(c *gin.Context) { } } +// @Tags App +// @Summary List app installed +// @Description 获取已安装应用列表 +// @Accept json +// @Success 200 array dto.AppInstallInfo +// @Security ApiKeyAuth +// @Router /apps/installed/list [get] +func (b *BaseApi) ListAppInstalled(c *gin.Context) { + list, err := appInstallService.GetInstallList() + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, list) +} + // @Tags App // @Summary Check app installed // @Description 检查应用安装情况 diff --git a/backend/app/dto/app.go b/backend/app/dto/app.go index 395060ffa..75ff0897c 100644 --- a/backend/app/dto/app.go +++ b/backend/app/dto/app.go @@ -132,3 +132,9 @@ var AppToolMap = map[string]string{ "mysql": "phpmyadmin", "redis": "redis-commander", } + +type AppInstallInfo struct { + ID uint `json:"id"` + Key string `json:"key"` + Name string `json:"name"` +} diff --git a/backend/app/dto/cronjob.go b/backend/app/dto/cronjob.go index 803560e3f..cd838ab2f 100644 --- a/backend/app/dto/cronjob.go +++ b/backend/app/dto/cronjob.go @@ -14,6 +14,7 @@ type CronjobCreate struct { Script string `json:"script"` ContainerName string `json:"containerName"` + AppID string `json:"appID"` Website string `json:"website"` ExclusionRules string `json:"exclusionRules"` DBName string `json:"dbName"` @@ -36,6 +37,7 @@ type CronjobUpdate struct { Script string `json:"script"` ContainerName string `json:"containerName"` + AppID string `json:"appID"` Website string `json:"website"` ExclusionRules string `json:"exclusionRules"` DBName string `json:"dbName"` @@ -79,6 +81,7 @@ type CronjobInfo struct { Script string `json:"script"` ContainerName string `json:"containerName"` + AppID string `json:"appID"` Website string `json:"website"` ExclusionRules string `json:"exclusionRules"` DBName string `json:"dbName"` diff --git a/backend/app/model/cronjob.go b/backend/app/model/cronjob.go index 3bc2ca30f..20dec4c16 100644 --- a/backend/app/model/cronjob.go +++ b/backend/app/model/cronjob.go @@ -18,6 +18,7 @@ type Cronjob struct { ContainerName string `gorm:"type:varchar(64)" json:"containerName"` Script string `gorm:"longtext" json:"script"` Website string `gorm:"type:varchar(64)" json:"website"` + AppID string `gorm:"type:varchar(64)" json:"appID"` DBName string `gorm:"type:varchar(64)" json:"dbName"` URL string `gorm:"type:varchar(256)" json:"url"` SourceDir string `gorm:"type:varchar(256)" json:"sourceDir"` diff --git a/backend/app/service/app_install.go b/backend/app/service/app_install.go index f47971b19..bd2cf5988 100644 --- a/backend/app/service/app_install.go +++ b/backend/app/service/app_install.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/1Panel-dev/1Panel/backend/i18n" "math" "os" "path" @@ -12,6 +11,8 @@ import ( "strconv" "strings" + "github.com/1Panel-dev/1Panel/backend/i18n" + "github.com/1Panel-dev/1Panel/backend/utils/files" "gopkg.in/yaml.v3" @@ -54,12 +55,26 @@ type IAppInstallService interface { ChangeAppPort(req request.PortUpdate) error GetDefaultConfigByKey(key string) (string, error) DeleteCheck(installId uint) ([]dto.AppResource, error) + + GetInstallList() ([]dto.AppInstallInfo, error) } func NewIAppInstalledService() IAppInstallService { return &AppInstallService{} } +func (a *AppInstallService) GetInstallList() ([]dto.AppInstallInfo, error) { + var datas []dto.AppInstallInfo + appInstalls, err := appInstallRepo.ListBy() + if err != nil { + return nil, err + } + for _, install := range appInstalls { + datas = append(datas, dto.AppInstallInfo{ID: install.ID, Key: install.App.Key, Name: install.Name}) + } + return datas, nil +} + func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) { var ( opts []repo.DBOption diff --git a/backend/app/service/cornjob.go b/backend/app/service/cornjob.go index 8e6eb89de..6e9ca4d37 100644 --- a/backend/app/service/cornjob.go +++ b/backend/app/service/cornjob.go @@ -46,7 +46,7 @@ func (u *CronjobService) SearchWithPage(search dto.SearchWithPage) (int64, inter if err := copier.Copy(&item, &cronjob); err != nil { return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) } - if item.Type == "website" || item.Type == "database" || item.Type == "directory" { + if item.Type == "app" || item.Type == "website" || item.Type == "database" || item.Type == "directory" { backup, _ := backupRepo.Get(commonRepo.WithByID(uint(item.TargetDirID))) if len(backup.Type) != 0 { item.TargetDir = backup.Type @@ -103,7 +103,7 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error { if err != nil { return err } - if req.CleanData && (cronjob.Type == "database" || cronjob.Type == "website" || cronjob.Type == "directory") { + if req.CleanData && (cronjob.Type == "app" || cronjob.Type == "database" || cronjob.Type == "website" || cronjob.Type == "directory") { cronjob.RetainCopies = 0 backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID))) if err != nil { diff --git a/backend/app/service/cronjob_helper.go b/backend/app/service/cronjob_helper.go index dea52f6e7..b1b81fec3 100644 --- a/backend/app/service/cronjob_helper.go +++ b/backend/app/service/cronjob_helper.go @@ -47,9 +47,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) { case "ntp": err = u.handleNtpSync() u.HandleRmExpired("LOCAL", "", "", cronjob, nil) - case "website": - record.File, err = u.handleBackup(cronjob, record.StartTime) - case "database": + case "website", "database", "app": record.File, err = u.handleBackup(cronjob, record.StartTime) case "directory": if len(cronjob.SourceDir) == 0 { @@ -120,6 +118,9 @@ func (u *CronjobService) handleBackup(cronjob *model.Cronjob, startTime time.Tim case "database": paths, err := u.handleDatabase(*cronjob, backup, startTime) return strings.Join(paths, ","), err + case "app": + paths, err := u.handleApp(*cronjob, backup, startTime) + return strings.Join(paths, ","), err case "website": paths, err := u.handleWebsite(*cronjob, backup, startTime) return strings.Join(paths, ","), err @@ -221,7 +222,7 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error { path = sourceDir } - commands := fmt.Sprintf("tar --warning=no-file-changed -zcf %s %s %s", targetDir+"/"+name, excludeRules, path) + commands := fmt.Sprintf("tar -zcf %s %s %s", targetDir+"/"+name, excludeRules, path) global.LOG.Debug(commands) stdout, err := cmd.ExecWithTimeOut(commands, 24*time.Hour) if err != nil { @@ -396,6 +397,84 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t return strings.Join(filePaths, ","), nil } +func (u *CronjobService) handleApp(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) { + var paths []string + localDir, err := loadLocalDir() + if err != nil { + return paths, err + } + + var applist []model.AppInstall + if cronjob.AppID == "all" { + applist, err = appInstallRepo.ListBy() + if err != nil { + return paths, err + } + } else { + itemID, _ := (strconv.Atoi(cronjob.AppID)) + app, err := appInstallRepo.GetFirst(commonRepo.WithByID(uint(itemID))) + if err != nil { + return paths, err + } + applist = append(applist, app) + } + + var client cloud_storage.CloudStorageClient + if backup.Type != "LOCAL" { + client, err = NewIBackupService().NewClient(&backup) + if err != nil { + return paths, err + } + } + + for _, app := range applist { + var record model.BackupRecord + record.Type = "app" + record.Name = app.App.Key + record.DetailName = app.Name + record.Source = "LOCAL" + record.BackupType = backup.Type + backupDir := path.Join(localDir, fmt.Sprintf("app/%s/%s", app.App.Key, app.Name)) + record.FileDir = backupDir + itemFileDir := strings.TrimPrefix(backupDir, localDir+"/") + if !cronjob.KeepLocal && backup.Type != "LOCAL" { + record.Source = backup.Type + record.FileDir = strings.TrimPrefix(backupDir, localDir+"/") + } + record.FileName = fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format("20060102150405")) + if err := handleAppBackup(&app, backupDir, record.FileName); err != nil { + return paths, err + } + record.Name = app.Name + if err := backupRepo.CreateRecord(&record); err != nil { + global.LOG.Errorf("save backup record failed, err: %v", err) + return paths, err + } + if backup.Type != "LOCAL" { + if !cronjob.KeepLocal { + defer func() { + _ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName)) + }() + } + if len(backup.BackupPath) != 0 { + itemPath := strings.TrimPrefix(backup.BackupPath, "/") + itemPath = strings.TrimSuffix(itemPath, "/") + "/" + itemFileDir = itemPath + itemFileDir + } + if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil { + return paths, err + } + } + if backup.Type == "LOCAL" || cronjob.KeepLocal { + paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName)) + } else { + paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName)) + } + } + u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client) + return paths, nil +} + func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) { var paths []string localDir, err := loadLocalDir() diff --git a/backend/init/migration/migrations/init.go b/backend/init/migration/migrations/init.go index d90316252..cecce5dc4 100644 --- a/backend/init/migration/migrations/init.go +++ b/backend/init/migration/migrations/init.go @@ -572,9 +572,9 @@ var UpdateCronjobWithDb = &gormigrate.Migration{ } var AddTableFirewall = &gormigrate.Migration{ - ID: "20230821-add-table-firewall", + ID: "20230823-add-table-firewall", Migrate: func(tx *gorm.DB) error { - if err := tx.AutoMigrate(&model.Firewall{}, model.SnapshotStatus{}); err != nil { + if err := tx.AutoMigrate(&model.Firewall{}, model.SnapshotStatus{}, &model.Cronjob{}); err != nil { return err } return nil diff --git a/backend/router/ro_app.go b/backend/router/ro_app.go index 0e35cc0c9..a52027099 100644 --- a/backend/router/ro_app.go +++ b/backend/router/ro_app.go @@ -29,6 +29,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) { appRouter.GET("/installed/conninfo/:key", baseApi.LoadConnInfo) appRouter.GET("/installed/delete/check/:appInstallId", baseApi.DeleteCheck) appRouter.POST("/installed/search", baseApi.SearchAppInstalled) + appRouter.GET("/installed/list", baseApi.ListAppInstalled) appRouter.POST("/installed/op", baseApi.OperateInstalled) appRouter.POST("/installed/sync", baseApi.SyncInstalled) appRouter.POST("/installed/port/change", baseApi.ChangeAppPort) diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index dda1852eb..48277d00e 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -460,6 +460,34 @@ const docTemplate = `{ } } }, + "/apps/installed/list": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取已安装应用列表", + "consumes": [ + "application/json" + ], + "tags": [ + "App" + ], + "summary": "List app installed", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.AppInstallInfo" + } + } + } + } + } + }, "/apps/installed/loadport/:key": { "get": { "security": [ @@ -689,14 +717,14 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "获取已安装应用列表", + "description": "分页获取已安装应用列表", "consumes": [ "application/json" ], "tags": [ "App" ], - "summary": "List app installed", + "summary": "Page app installed", "parameters": [ { "description": "request", @@ -11580,6 +11608,20 @@ const docTemplate = `{ } } }, + "dto.AppInstallInfo": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "dto.AppResource": { "type": "object", "properties": { @@ -12213,6 +12255,9 @@ const docTemplate = `{ "type" ], "properties": { + "appID": { + "type": "string" + }, "containerName": { "type": "string" }, @@ -12295,6 +12340,9 @@ const docTemplate = `{ "specType" ], "properties": { + "appID": { + "type": "string" + }, "containerName": { "type": "string" }, diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index 845eff5d9..aafcebab0 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -453,6 +453,34 @@ } } }, + "/apps/installed/list": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取已安装应用列表", + "consumes": [ + "application/json" + ], + "tags": [ + "App" + ], + "summary": "List app installed", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.AppInstallInfo" + } + } + } + } + } + }, "/apps/installed/loadport/:key": { "get": { "security": [ @@ -682,14 +710,14 @@ "ApiKeyAuth": [] } ], - "description": "获取已安装应用列表", + "description": "分页获取已安装应用列表", "consumes": [ "application/json" ], "tags": [ "App" ], - "summary": "List app installed", + "summary": "Page app installed", "parameters": [ { "description": "request", @@ -11573,6 +11601,20 @@ } } }, + "dto.AppInstallInfo": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "dto.AppResource": { "type": "object", "properties": { @@ -12206,6 +12248,9 @@ "type" ], "properties": { + "appID": { + "type": "string" + }, "containerName": { "type": "string" }, @@ -12288,6 +12333,9 @@ "specType" ], "properties": { + "appID": { + "type": "string" + }, "containerName": { "type": "string" }, diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index 7dc226355..d2e96d15b 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -28,6 +28,15 @@ definitions: oldRule: $ref: '#/definitions/dto.AddrRuleOperate' type: object + dto.AppInstallInfo: + properties: + id: + type: integer + key: + type: string + name: + type: string + type: object dto.AppResource: properties: name: @@ -449,6 +458,8 @@ definitions: type: object dto.CronjobCreate: properties: + appID: + type: string containerName: type: string day: @@ -505,6 +516,8 @@ definitions: type: object dto.CronjobUpdate: properties: + appID: + type: string containerName: type: string day: @@ -4177,6 +4190,23 @@ paths: formatEN: Application param update [installId] formatZH: 忽略应用 [installId] 版本升级 paramKeys: [] + /apps/installed/list: + get: + consumes: + - application/json + description: 获取已安装应用列表 + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/dto.AppInstallInfo' + type: array + security: + - ApiKeyAuth: [] + summary: List app installed + tags: + - App /apps/installed/loadport/:key: get: consumes: @@ -4325,7 +4355,7 @@ paths: post: consumes: - application/json - description: 获取已安装应用列表 + description: 分页获取已安装应用列表 parameters: - description: request in: body @@ -4338,7 +4368,7 @@ paths: description: OK security: - ApiKeyAuth: [] - summary: List app installed + summary: Page app installed tags: - App /apps/installed/sync: diff --git a/frontend/src/api/interface/app.ts b/frontend/src/api/interface/app.ts index 61e456e26..f067a754e 100644 --- a/frontend/src/api/interface/app.ts +++ b/frontend/src/api/interface/app.ts @@ -116,6 +116,12 @@ export namespace App { app: App; } + export interface AppInstalledInfo { + id: number; + key: string; + name: string; + } + export interface CheckInstalled { name: string; version: string; diff --git a/frontend/src/api/interface/cronjob.ts b/frontend/src/api/interface/cronjob.ts index f6e6e13f7..78df708bf 100644 --- a/frontend/src/api/interface/cronjob.ts +++ b/frontend/src/api/interface/cronjob.ts @@ -15,6 +15,7 @@ export namespace Cronjob { script: string; inContainer: boolean; containerName: string; + appID: string; website: string; exclusionRules: string; dbName: string; diff --git a/frontend/src/api/modules/app.ts b/frontend/src/api/modules/app.ts index 989f3edf9..6315787d1 100644 --- a/frontend/src/api/modules/app.ts +++ b/frontend/src/api/modules/app.ts @@ -42,6 +42,10 @@ export const SearchAppInstalled = (search: App.AppInstallSearch) => { return http.post>('apps/installed/search', search); }; +export const ListAppInstalled = () => { + return http.get>('apps/installed/list'); +}; + export const GetAppPort = (key: string) => { return http.get(`apps/installed/loadport/${key}`); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 8fa19cd0f..13bd5336c 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -689,6 +689,7 @@ const message = { containerCheckBox: 'In container (no need to enter the container command)', containerName: 'Container name', ntp: 'Time synchronization', + 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', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 2f67af6f6..7b413b985 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -665,6 +665,7 @@ const message = { containerCheckBox: '在容器中執行(無需再輸入進入容器命令)', containerName: '容器名稱', ntp: '時間同步', + app: '備份應用', website: '備份網站', rulesHelper: '當存在多個壓縮排除規則時,需要換行顯示,例:\n*.log \n*.sql', lastRecordTime: '上次執行時間', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index a91b4d619..e79e241cb 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -665,6 +665,7 @@ const message = { containerCheckBox: '在容器中执行(无需再输入进入容器命令)', containerName: '容器名称', ntp: '时间同步', + app: '备份应用', website: '备份网站', rulesHelper: '当存在多个压缩排除规则时,需要换行显示,例:\n*.log \n*.sql', lastRecordTime: '上次执行时间', diff --git a/frontend/src/views/cronjob/operate/index.vue b/frontend/src/views/cronjob/operate/index.vue index ca03e140c..48a7f546f 100644 --- a/frontend/src/views/cronjob/operate/index.vue +++ b/frontend/src/views/cronjob/operate/index.vue @@ -19,6 +19,7 @@ v-model="dialogData.rowData!.type" > + @@ -118,6 +119,17 @@ +
+ + + +
+ +
+
+
+
+
@@ -237,6 +249,7 @@ import { MsgError, MsgSuccess } from '@/utils/message'; import { useRouter } from 'vue-router'; import { listContainer } from '@/api/modules/container'; import { Database } from '@/api/interface/database'; +import { ListAppInstalled } from '@/api/modules/app'; const router = useRouter(); interface DialogProps { @@ -264,6 +277,7 @@ const acceptParams = (params: DialogProps): void => { drawerVisiable.value = true; checkMysqlInstalled(); loadBackups(); + loadAppInstalls(); loadWebsites(); loadContainers(); }; @@ -282,6 +296,7 @@ const localDirID = ref(); const containerOptions = ref(); const websiteOptions = ref(); const backupOptions = ref(); +const appOptions = ref(); const mysqlInfo = reactive({ isExist: false, @@ -417,6 +432,11 @@ const changeType = () => { dialogData.value.rowData.hour = 1; dialogData.value.rowData.minute = 30; break; + case 'app': + dialogData.value.rowData.specType = 'perDay'; + dialogData.value.rowData.hour = 2; + dialogData.value.rowData.minute = 30; + break; case 'database': dialogData.value.rowData.specType = 'perDay'; dialogData.value.rowData.hour = 2; @@ -459,6 +479,11 @@ const loadBackups = async () => { } }; +const loadAppInstalls = async () => { + const res = await ListAppInstalled(); + appOptions.value = res.data || []; +}; + const loadWebsites = async () => { const res = await GetWebsiteOptions(); websiteOptions.value = res.data || []; @@ -476,6 +501,7 @@ const checkMysqlInstalled = async () => { function isBackup() { return ( + dialogData.value.rowData!.type === 'app' || dialogData.value.rowData!.type === 'website' || dialogData.value.rowData!.type === 'database' || dialogData.value.rowData!.type === 'directory' diff --git a/frontend/src/views/cronjob/record/index.vue b/frontend/src/views/cronjob/record/index.vue index e8d80b51c..acc063e92 100644 --- a/frontend/src/views/cronjob/record/index.vue +++ b/frontend/src/views/cronjob/record/index.vue @@ -177,6 +177,17 @@ {{ $t('file.download') }} + + + + {{ dialogData.rowData!.appID }} + + + {{ $t('commons.table.all') }} + +